From 459e9d5fbe83504a88efd83909af93e62b873929 Mon Sep 17 00:00:00 2001 From: Difegue Date: Sat, 13 Apr 2024 01:49:05 +0200 Subject: [PATCH 01/25] Fix crash reported by appcenter --- .../ViewModels/Bases/LibraryViewModelBase.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/Stylophone.Common/ViewModels/Bases/LibraryViewModelBase.cs b/Sources/Stylophone.Common/ViewModels/Bases/LibraryViewModelBase.cs index 2d7fec3..256e884 100644 --- a/Sources/Stylophone.Common/ViewModels/Bases/LibraryViewModelBase.cs +++ b/Sources/Stylophone.Common/ViewModels/Bases/LibraryViewModelBase.cs @@ -50,11 +50,12 @@ public async Task LoadDataAsync() var albumList = await _mpdService.SafelySendCommandAsync(new ListCommand(MpdTags.Album)); var albumSortList = await _mpdService.SafelySendCommandAsync(new ListCommand(MpdTags.AlbumSort)); - // Create a list of tuples - var response = albumList.Zip(albumSortList, (album, albumSort) => new Album{ Name = album, SortName = albumSort }); - - if (albumSortList != null) + if (albumList != null && albumSortList != null) + { + // Create a list of tuples + var response = albumList.Zip(albumSortList, (album, albumSort) => new Album { Name = album, SortName = albumSort }); GroupAlbumsByName(response); + } if (Source.Count > 0) FilteredSource.AddRange(Source); From bc0481b467e07eebf9eccd0f39d04a0e956f5485 Mon Sep 17 00:00:00 2001 From: Difegue Date: Wed, 17 Apr 2024 02:25:49 +0200 Subject: [PATCH 02/25] Migrate to NET8 for the app to not horribly crash when targeting iOS17.4 ie https://github.com/dotnet/runtime/issues/98941 --- .../Stylophone.iOS/Helpers/TrackTableViewDataSource.cs | 8 ++++++++ Sources/Stylophone.iOS/Info.plist | 6 +++--- Sources/Stylophone.iOS/Stylophone.iOS.csproj | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/Stylophone.iOS/Helpers/TrackTableViewDataSource.cs b/Sources/Stylophone.iOS/Helpers/TrackTableViewDataSource.cs index 3bd0759..861a1c6 100644 --- a/Sources/Stylophone.iOS/Helpers/TrackTableViewDataSource.cs +++ b/Sources/Stylophone.iOS/Helpers/TrackTableViewDataSource.cs @@ -135,6 +135,14 @@ public void MoveRow(UITableView tableView, NSIndexPath sourceIndexPath, NSIndexP #region Delegate + public override bool ShouldBeginMultipleSelectionInteraction(UITableView tableView, NSIndexPath indexPath) + => _canReorder; + + public override void DidBeginMultipleSelectionInteraction(UITableView tableView, NSIndexPath indexPath) + { + tableView.SetEditing(true, true); + } + // If multiselect isn't enabled, this will show a delete icon on the left side of the cells public override UITableViewCellEditingStyle EditingStyleForRow(UITableView tableView, NSIndexPath indexPath) => UITableViewCellEditingStyle.Delete; diff --git a/Sources/Stylophone.iOS/Info.plist b/Sources/Stylophone.iOS/Info.plist index ca94302..b50512f 100644 --- a/Sources/Stylophone.iOS/Info.plist +++ b/Sources/Stylophone.iOS/Info.plist @@ -16,11 +16,11 @@ zh CFBundleShortVersionString - 2.6.1 + 2.6.0 CFBundleVersion - 2.6.1 + 2.6.0.2 LSRequiresIPhoneOS - + MinimumOSVersion 16.0 UIBackgroundModes diff --git a/Sources/Stylophone.iOS/Stylophone.iOS.csproj b/Sources/Stylophone.iOS/Stylophone.iOS.csproj index 6277b64..3d7d41f 100644 --- a/Sources/Stylophone.iOS/Stylophone.iOS.csproj +++ b/Sources/Stylophone.iOS/Stylophone.iOS.csproj @@ -1,6 +1,6 @@  - net7.0-ios;net7.0-maccatalyst + net8.0-maccatalyst;net8.0-ios Exe enable true From f7051f46f2b9c677ee1792e22cf4870b0f2312e3 Mon Sep 17 00:00:00 2001 From: Difegue Date: Wed, 17 Apr 2024 02:26:17 +0200 Subject: [PATCH 03/25] Disable silencePlayer on catalyst (might be needed again later but I mostly just need to commit a code example for catalyst detection...) --- .../Stylophone.iOS/Services/NowPlayingService.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/Stylophone.iOS/Services/NowPlayingService.cs b/Sources/Stylophone.iOS/Services/NowPlayingService.cs index 2dbd4fc..2750fc4 100644 --- a/Sources/Stylophone.iOS/Services/NowPlayingService.cs +++ b/Sources/Stylophone.iOS/Services/NowPlayingService.cs @@ -21,7 +21,7 @@ public class NowPlayingService private MPDConnectionService _mpdService; private IApplicationStorageService _storageService; - private AVAudioPlayer _silencePlayer; + private AVAudioPlayer? _silencePlayer; public NowPlayingService(MPDConnectionService mpdService, IApplicationStorageService storageService) { @@ -30,6 +30,9 @@ public NowPlayingService(MPDConnectionService mpdService, IApplicationStorageSer // https://stackoverflow.com/questions/48289037/using-mpnowplayinginfocenter-without-actually-playing-audio // TODO This breaks when LibVLC playback stops + if (new NSProcessInfo().IsMacCatalystApplication) + return; + _silencePlayer = new AVAudioPlayer(new NSUrl("silence.wav",false,NSBundle.MainBundle.ResourceUrl), null, out var error); _silencePlayer.NumberOfLoops = -1; } @@ -111,19 +114,19 @@ private void UpdateState(MpdStatus status) switch (status.State) { case MpdState.Play: - _silencePlayer.Play(); + _silencePlayer?.Play(); _nowPlayingInfo.PlaybackRate = 1; break; case MpdState.Pause: - _silencePlayer.Stop(); + _silencePlayer?.Stop(); _nowPlayingInfo.PlaybackRate = 0; break; case MpdState.Stop: - _silencePlayer.Stop(); + _silencePlayer?.Stop(); _nowPlayingInfo.PlaybackRate = 0; break; case MpdState.Unknown: - _silencePlayer.Stop(); + _silencePlayer?.Stop(); _nowPlayingInfo.PlaybackRate = 0; break; default: From fc0ccaf2e8d1dfbb8d66ffc906397dc607a201cf Mon Sep 17 00:00:00 2001 From: Difegue <8237712+Difegue@users.noreply.github.com> Date: Sun, 21 Apr 2024 02:30:46 +0200 Subject: [PATCH 04/25] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c04b8de..8caeccc 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Based on [MpcNET](https://github.com/Difegue/MpcNET), my own fork of the origina * Playlist management (Create, Add/Remove tracks, Delete) * Picture-in-picture mode * Live tile on Windows 10 +* Local playback support if your MPD server has `httpd` as an output * Integration with native playback controls * Browse library by albums, or directly by folders * All data is pulled from your MPD Server only From 5862411bf991aff4f604513ffd2b41424c50e4e5 Mon Sep 17 00:00:00 2001 From: Difegue Date: Thu, 15 Aug 2024 15:47:44 +0200 Subject: [PATCH 05/25] Add xcprivacy file for future iOS updates --- .../Resources/PrivacyInfo.xcprivacy | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Sources/Stylophone.iOS/Resources/PrivacyInfo.xcprivacy diff --git a/Sources/Stylophone.iOS/Resources/PrivacyInfo.xcprivacy b/Sources/Stylophone.iOS/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..dd61db7 --- /dev/null +++ b/Sources/Stylophone.iOS/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,43 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + + + \ No newline at end of file From 039e99d63aa663faef2ae6fefde9c05766a3f937 Mon Sep 17 00:00:00 2001 From: Difegue <8237712+Difegue@users.noreply.github.com> Date: Tue, 13 Aug 2024 00:36:33 +0200 Subject: [PATCH 06/25] Add widget template --- Sources/Stylophone/Assets/widgetTemplate.json | 107 ++++++++++++++++++ Sources/Stylophone/Package.appxmanifest | 2 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 Sources/Stylophone/Assets/widgetTemplate.json diff --git a/Sources/Stylophone/Assets/widgetTemplate.json b/Sources/Stylophone/Assets/widgetTemplate.json new file mode 100644 index 0000000..a61b19a --- /dev/null +++ b/Sources/Stylophone/Assets/widgetTemplate.json @@ -0,0 +1,107 @@ +{ + "type": "AdaptiveCard", + "body": [ + { + "type": "Image", + "url": "${stylophone_albumArt}", + "altText": "${stylophone_album}", + "size": "Large", + "horizontalAlignment": "Center", + "$when": "${$host.widgetSize==\"medium\"}" + }, + { + "type": "Image", + "url": "${stylophone_albumArt}", + "altText": "${stylophone_album}", + "$when": "${$host.widgetSize==\"large\"}" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "Image", + "url": "${stylophone_albumArt}", + "altText": "${stylophone_album}", + "size": "Medium", + "width": "80px" + } + ], + "$when": "${$host.widgetSize==\"small\"}", + "width": "auto" + }, + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "size": "Medium", + "weight": "Bolder", + "text": "${stylophone_title}", + "wrap": true, + "maxLines": 2 + }, + { + "type": "TextBlock", + "text": "${stylophone_artist}", + "wrap": true, + "weight": "Lighter" + }, + { + "type": "TextBlock", + "spacing": "None", + "text": "${stylophone_album}", + "isSubtle": true + } + ], + "width": "stretch" + } + ] + }, + { + "type": "ActionSet", + "actions": [ + { + "type": "Action.Execute", + "mode": "secondary", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e8b1.png", + "tooltip": "Shuffle", + "verb": "stylophone_shuffle" + }, + { + "type": "Action.Execute", + "mode": "secondary", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e892.png", + "tooltip": "Previous", + "verb": "stylophone_prev" + }, + { + "type": "Action.Execute", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/edb4.png", + "tooltip": "Pause", + "verb": "stylophone_pause" + }, + { + "type": "Action.Execute", + "mode": "secondary", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e893.png", + "tooltip": "Next", + "verb": "stylophone_next" + }, + { + "type": "Action.Execute", + "mode": "secondary", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e8ee.png", + "tooltip": "Repeat", + "verb": "stylophone_repeat" + } + ], + "spacing": "Small", + "horizontalAlignment": "Center" + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.6" +} \ No newline at end of file diff --git a/Sources/Stylophone/Package.appxmanifest b/Sources/Stylophone/Package.appxmanifest index 93e2f24..90c18cd 100644 --- a/Sources/Stylophone/Package.appxmanifest +++ b/Sources/Stylophone/Package.appxmanifest @@ -12,7 +12,7 @@ + Version="2.6.2.0" /> From 2984e76140276fb44c91c47d2f71c624c95e76fc Mon Sep 17 00:00:00 2001 From: Difegue <8237712+Difegue@users.noreply.github.com> Date: Tue, 13 Aug 2024 23:09:06 +0200 Subject: [PATCH 07/25] Add protocol activation handler --- .../Activation/DefaultActivationHandler.cs | 46 +++- .../Activation/ProtocolActivationHandler.cs | 106 ++++++++++ Sources/Stylophone/App.xaml.cs | 7 +- Sources/Stylophone/Assets/widgetTemplate.json | 197 +++++++++--------- Sources/Stylophone/Package.appxmanifest | 6 + Sources/Stylophone/Package.tt | 6 + .../Stylophone/Services/ActivationService.cs | 73 ++----- Sources/Stylophone/Stylophone.csproj | 1 + 8 files changed, 278 insertions(+), 164 deletions(-) create mode 100644 Sources/Stylophone/Activation/ProtocolActivationHandler.cs diff --git a/Sources/Stylophone/Activation/DefaultActivationHandler.cs b/Sources/Stylophone/Activation/DefaultActivationHandler.cs index 882f88e..76e2f49 100644 --- a/Sources/Stylophone/Activation/DefaultActivationHandler.cs +++ b/Sources/Stylophone/Activation/DefaultActivationHandler.cs @@ -3,6 +3,12 @@ using Stylophone.Services; using Stylophone.Common.Interfaces; using Windows.ApplicationModel.Activation; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml; +using CommunityToolkit.Mvvm.DependencyInjection; +using Stylophone.Common.Services; +using Stylophone.Common.ViewModels; +using System.Threading; namespace Stylophone.Activation { @@ -29,13 +35,49 @@ protected override async Task HandleInternalAsync(IActivatedEventArgs args) } _navigationService.Navigate(_navElement, arguments); - await Task.CompletedTask; + + // Ensure the current window is active + Window.Current.Activate(); + + // Tasks after activation + await StartupAsync(); + } + + private async Task StartupAsync() + { + var theme = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ElementTheme)); + Enum.TryParse(theme, out Theme elementTheme); + await Ioc.Default.GetRequiredService().SetThemeAsync(elementTheme); + + await Ioc.Default.GetRequiredService().ShowFirstRunDialogIfAppropriateAsync(); + + _ = Task.Run(async () => + { + Thread.Sleep(60000); + await Ioc.Default.GetRequiredService().ShowRateAppDialogIfAppropriateAsync(); + }); + + var host = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerHost)); + host = host?.Replace("\"", ""); // TODO: This is a quickfix for 1.x updates + var port = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerPort), 6600); + var pass = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerPassword)); + var localPlaybackEnabled = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.IsLocalPlaybackEnabled)); + + var localPlaybackVm = Ioc.Default.GetRequiredService(); + localPlaybackVm.Initialize(host, localPlaybackEnabled); + + var mpdService = Ioc.Default.GetRequiredService(); + mpdService.SetServerInfo(host, port, pass); + await mpdService.InitializeAsync(true); + + Ioc.Default.GetRequiredService().Initialize(); + Ioc.Default.GetRequiredService().Initialize(); } protected override bool CanHandleInternal(IActivatedEventArgs args) { // None of the ActivationHandlers has handled the app activation - return ((NavigationService)_navigationService).Frame.Content == null && _navElement != null; + return ((NavigationService)_navigationService).Frame?.Content == null && _navElement != null; } } } diff --git a/Sources/Stylophone/Activation/ProtocolActivationHandler.cs b/Sources/Stylophone/Activation/ProtocolActivationHandler.cs new file mode 100644 index 0000000..70d0bc6 --- /dev/null +++ b/Sources/Stylophone/Activation/ProtocolActivationHandler.cs @@ -0,0 +1,106 @@ +using System; +using System.Threading.Tasks; +using Stylophone.Services; +using Stylophone.Common.Interfaces; +using Windows.ApplicationModel.Activation; +using CommunityToolkit.Mvvm.DependencyInjection; +using Stylophone.Common.Services; +using Windows.UI.Xaml.Controls; +using MpcNET.Commands.Playback; +using MpcNET.Commands.Status; +using MpcNET.Commands.Playlist; +using System.Xml.Linq; +using Stylophone.Common.ViewModels; +using Windows.Services.Maps; + +namespace Stylophone.Activation +{ + internal class ProtocolActivationHandler : ActivationHandler + { + private MPDConnectionService _mpdService; + + public ProtocolActivationHandler(MPDConnectionService connectionService) + { + _mpdService = connectionService; + } + + protected override async Task HandleInternalAsync(IActivatedEventArgs args) + { + ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs; + + if (!_mpdService.IsConnected) + { + var host = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerHost)); + host = host?.Replace("\"", ""); // TODO: This is a quickfix for 1.x updates + var port = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerPort), 6600); + var pass = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerPassword)); + _mpdService.SetServerInfo(host, port, pass); + await _mpdService.InitializeAsync(false); + } + + if (!_mpdService.IsConnected) + { + var msgbox = new ContentDialog + { + Title = "Couldn't connect to MPD server", + Content = "Please open Stylophone and configure a MPD server.", + CloseButtonText = "OK" + }; + await msgbox.ShowAsync(); + return; + } + + var status = _mpdService.CurrentStatus == MPDConnectionService.BOGUS_STATUS ? await _mpdService.SafelySendCommandAsync(new StatusCommand()) : _mpdService.CurrentStatus; + + // Protocol launches can do basic operations on the MPD server based on the verb + // eg stylophone://?verb=stylophone_play + var uri = new Uri(eventArgs.Uri.AbsoluteUri); + var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query); + + switch (queryDictionary["verb"]) + { + case "stylophone_play": + case "stylophone_pause": + await _mpdService.SafelySendCommandAsync(new PauseResumeCommand()); + break; + case "stylophone_stop": + await _mpdService.SafelySendCommandAsync(new StopCommand()); + break; + case "stylophone_next": + await _mpdService.SafelySendCommandAsync(new NextCommand()); + break; + case "stylophone_prev": + await _mpdService.SafelySendCommandAsync(new PreviousCommand()); + break; + case "stylophone_shuffle": + await _mpdService.SafelySendCommandAsync(new RandomCommand(!status.Random)); + break; + case "stylophone_volume_up": + await _mpdService.SafelySendCommandAsync(new SetVolumeCommand((byte)(status.Volume + 5))); + break; + case "stylophone_volume_down": + await _mpdService.SafelySendCommandAsync(new SetVolumeCommand((byte)(status.Volume - 5))); + break; + case "stylophone_volume_set": + var volume = queryDictionary["volume"] ?? "0"; + await _mpdService.SafelySendCommandAsync(new SetVolumeCommand((byte)(int.Parse(volume)))); + break; + case "stylophone_seek": + var seek = int.Parse(queryDictionary["volseekume"] ?? "0"); + await _mpdService.SafelySendCommandAsync(new SeekCurCommand(seek)); + break; + case "stylophone_load_playlist": + var playlist = queryDictionary["playlist"] ?? ""; + await _mpdService.SafelySendCommandAsync(new LoadCommand(playlist)); + break; + default: + break; + } + } + + protected override bool CanHandleInternal(IActivatedEventArgs args) + { + return args.Kind == ActivationKind.Protocol; + } + } +} diff --git a/Sources/Stylophone/App.xaml.cs b/Sources/Stylophone/App.xaml.cs index 4e675b1..ea82f55 100644 --- a/Sources/Stylophone/App.xaml.cs +++ b/Sources/Stylophone/App.xaml.cs @@ -136,12 +136,7 @@ private void OnAppSuspending(object sender, SuspendingEventArgs e) private ActivationService CreateActivationService() { - return new ActivationService(this, typeof(QueueViewModel), new Lazy(CreateShell)); - } - - private UIElement CreateShell() - { - return new Views.ShellPage(); + return new ActivationService(this, typeof(QueueViewModel)); } protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args) diff --git a/Sources/Stylophone/Assets/widgetTemplate.json b/Sources/Stylophone/Assets/widgetTemplate.json index a61b19a..2a2e4f1 100644 --- a/Sources/Stylophone/Assets/widgetTemplate.json +++ b/Sources/Stylophone/Assets/widgetTemplate.json @@ -1,107 +1,110 @@ { - "type": "AdaptiveCard", - "body": [ + "type": "AdaptiveCard", + "body": [ + { + "type": "Image", + "url": "${stylophone_albumArt}", + "altText": "${stylophone_album}", + "size": "Large", + "horizontalAlignment": "Left", + "$when": "${$host.widgetSize==\"medium\"}" + }, + { + "type": "Image", + "url": "${stylophone_albumArt}", + "altText": "${stylophone_album}", + "$when": "${$host.widgetSize==\"large\"}" + }, + { + "type": "ColumnSet", + "columns": [ { - "type": "Image", - "url": "${stylophone_albumArt}", - "altText": "${stylophone_album}", - "size": "Large", - "horizontalAlignment": "Center", - "$when": "${$host.widgetSize==\"medium\"}" + "type": "Column", + "items": [ + { + "type": "Image", + "url": "${stylophone_albumArt}", + "altText": "${stylophone_album}", + "size": "Medium", + "width": "80px" + } + ], + "$when": "${$host.widgetSize==\"small\"}", + "width": "auto" }, { - "type": "Image", - "url": "${stylophone_albumArt}", - "altText": "${stylophone_album}", - "$when": "${$host.widgetSize==\"large\"}" + "type": "Column", + "items": [ + { + "type": "TextBlock", + "size": "Medium", + "weight": "Bolder", + "text": "${stylophone_title}", + "wrap": true, + "maxLines": 2 + }, + { + "type": "TextBlock", + "text": "${stylophone_artist}", + "weight": "Lighter", + "isSubtle": true, + "spacing": "None" + }, + { + "type": "TextBlock", + "spacing": "None", + "text": "${stylophone_album}", + "isSubtle": true + } + ], + "width": "stretch", + "height": "stretch" + } + ] + }, + { + "type": "ActionSet", + "actions": [ + { + "type": "Action.Execute", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e8b1.png", + "tooltip": "Shuffle", + "verb": "stylophone_shuffle", + "$when": "${$host.widgetSize!=\"small\"}" + }, + { + "type": "Action.Execute", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e892.png", + "tooltip": "Previous", + "verb": "stylophone_prev", + "$when": "${$host.widgetSize!=\"small\"}" + }, + { + "type": "Action.Execute", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/edb4.png", + "tooltip": "Pause", + "verb": "stylophone_pause", + "$when": "${$host.widgetSize!=\"small\"}" }, { - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "items": [ - { - "type": "Image", - "url": "${stylophone_albumArt}", - "altText": "${stylophone_album}", - "size": "Medium", - "width": "80px" - } - ], - "$when": "${$host.widgetSize==\"small\"}", - "width": "auto" - }, - { - "type": "Column", - "items": [ - { - "type": "TextBlock", - "size": "Medium", - "weight": "Bolder", - "text": "${stylophone_title}", - "wrap": true, - "maxLines": 2 - }, - { - "type": "TextBlock", - "text": "${stylophone_artist}", - "wrap": true, - "weight": "Lighter" - }, - { - "type": "TextBlock", - "spacing": "None", - "text": "${stylophone_album}", - "isSubtle": true - } - ], - "width": "stretch" - } - ] + "type": "Action.Execute", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e893.png", + "tooltip": "Next", + "verb": "stylophone_next", + "$when": "${$host.widgetSize!=\"small\"}" }, { - "type": "ActionSet", - "actions": [ - { - "type": "Action.Execute", - "mode": "secondary", - "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e8b1.png", - "tooltip": "Shuffle", - "verb": "stylophone_shuffle" - }, - { - "type": "Action.Execute", - "mode": "secondary", - "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e892.png", - "tooltip": "Previous", - "verb": "stylophone_prev" - }, - { - "type": "Action.Execute", - "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/edb4.png", - "tooltip": "Pause", - "verb": "stylophone_pause" - }, - { - "type": "Action.Execute", - "mode": "secondary", - "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e893.png", - "tooltip": "Next", - "verb": "stylophone_next" - }, - { - "type": "Action.Execute", - "mode": "secondary", - "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e8ee.png", - "tooltip": "Repeat", - "verb": "stylophone_repeat" - } - ], - "spacing": "Small", - "horizontalAlignment": "Center" + "type": "Action.Execute", + "iconUrl": "https://learn.microsoft.com/en-us/windows/apps/design/style/images/segoe-mdl/e8ee.png", + "tooltip": "Repeat", + "verb": "stylophone_repeat", + "$when": "${$host.widgetSize!=\"small\"}" } - ], - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "version": "1.6" + ], + "spacing": "Small", + "horizontalAlignment": "Center" + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.6" } \ No newline at end of file diff --git a/Sources/Stylophone/Package.appxmanifest b/Sources/Stylophone/Package.appxmanifest index 90c18cd..eda0563 100644 --- a/Sources/Stylophone/Package.appxmanifest +++ b/Sources/Stylophone/Package.appxmanifest @@ -51,6 +51,12 @@ + + + Assets\Square44x44Logo.png + Stylophone URI Scheme + + diff --git a/Sources/Stylophone/Package.tt b/Sources/Stylophone/Package.tt index 70ff8db..9dc5bb6 100644 --- a/Sources/Stylophone/Package.tt +++ b/Sources/Stylophone/Package.tt @@ -101,6 +101,12 @@ + + + Assets\Square44x44Logo.png + Stylophone URI Scheme + + diff --git a/Sources/Stylophone/Services/ActivationService.cs b/Sources/Stylophone/Services/ActivationService.cs index 9bac2ba..beb0a22 100644 --- a/Sources/Stylophone/Services/ActivationService.cs +++ b/Sources/Stylophone/Services/ActivationService.cs @@ -10,6 +10,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using System.Threading; +using System.Diagnostics; namespace Stylophone.Services { @@ -19,88 +20,42 @@ internal class ActivationService { private readonly App _app; private readonly Type _defaultNavItem; - private Lazy _shell; - private object _lastActivationArgs; - - public ActivationService(App app, Type defaultNavItem, Lazy shell = null) + public ActivationService(App app, Type defaultNavItem) { _app = app; - _shell = shell; _defaultNavItem = defaultNavItem; } public async Task ActivateAsync(object activationArgs) { - if (IsInteractive(activationArgs)) + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (Window.Current.Content == null) { - // Do not repeat app initialization when the Window already has content, - // just ensure that the window is active - if (Window.Current.Content == null) - { - // Create a Shell or Frame to act as the navigation context - Window.Current.Content = _shell?.Value ?? new Frame(); - } + // Create a Shell to act as the navigation context + Window.Current.Content = new Views.ShellPage(); } - // Depending on activationArgs one of ActivationHandlers or DefaultActivationHandler - // will navigate to the first page + // Depending on activationArgs, ProtocolActivationHandler and DefaultActivationHandler will trigger await HandleActivationAsync(activationArgs); - _lastActivationArgs = activationArgs; - - if (IsInteractive(activationArgs)) - { - // Ensure the current window is active - Window.Current.Activate(); - - // Tasks after activation - await StartupAsync(); - } } private async Task HandleActivationAsync(object activationArgs) { + var defaultHandler = new DefaultActivationHandler(_defaultNavItem, Ioc.Default.GetRequiredService()); + var protocolHandler = new ProtocolActivationHandler(Ioc.Default.GetRequiredService()); + if (IsInteractive(activationArgs)) { - var defaultHandler = new DefaultActivationHandler(_defaultNavItem, Ioc.Default.GetRequiredService()); + if (protocolHandler.CanHandle(activationArgs)) + await protocolHandler.HandleAsync(activationArgs); + if (defaultHandler.CanHandle(activationArgs)) - { await defaultHandler.HandleAsync(activationArgs); - } } } - private async Task StartupAsync() - { - var theme = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ElementTheme)); - Enum.TryParse(theme, out Theme elementTheme); - await Ioc.Default.GetRequiredService().SetThemeAsync(elementTheme); - - await Ioc.Default.GetRequiredService().ShowFirstRunDialogIfAppropriateAsync(); - - _ = Task.Run(async () => - { - Thread.Sleep(60000); - await Ioc.Default.GetRequiredService().ShowRateAppDialogIfAppropriateAsync(); - }); - - var host = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerHost)); - host = host?.Replace("\"", ""); // TODO: This is a quickfix for 1.x updates - var port = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerPort), 6600); - var pass = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerPassword)); - var localPlaybackEnabled = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.IsLocalPlaybackEnabled)); - - var localPlaybackVm = Ioc.Default.GetRequiredService(); - localPlaybackVm.Initialize(host, localPlaybackEnabled); - - var mpdService = Ioc.Default.GetRequiredService(); - mpdService.SetServerInfo(host, port, pass); - await mpdService.InitializeAsync(true); - - Ioc.Default.GetRequiredService().Initialize(); - Ioc.Default.GetRequiredService().Initialize(); - } - private bool IsInteractive(object args) { return args is IActivatedEventArgs; diff --git a/Sources/Stylophone/Stylophone.csproj b/Sources/Stylophone/Stylophone.csproj index 89a8705..30d1dae 100644 --- a/Sources/Stylophone/Stylophone.csproj +++ b/Sources/Stylophone/Stylophone.csproj @@ -174,6 +174,7 @@ + From 4aa9db693073d8cb9063e31f196ca5852319a13b Mon Sep 17 00:00:00 2001 From: Difegue Date: Wed, 4 Sep 2024 02:26:42 +0200 Subject: [PATCH 08/25] Tweak protocol error message to work without shell --- .../Activation/ProtocolActivationHandler.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Sources/Stylophone/Activation/ProtocolActivationHandler.cs b/Sources/Stylophone/Activation/ProtocolActivationHandler.cs index 70d0bc6..0250415 100644 --- a/Sources/Stylophone/Activation/ProtocolActivationHandler.cs +++ b/Sources/Stylophone/Activation/ProtocolActivationHandler.cs @@ -12,6 +12,8 @@ using System.Xml.Linq; using Stylophone.Common.ViewModels; using Windows.Services.Maps; +using System.Diagnostics; +using Windows.UI.Popups; namespace Stylophone.Activation { @@ -40,13 +42,8 @@ protected override async Task HandleInternalAsync(IActivatedEventArgs args) if (!_mpdService.IsConnected) { - var msgbox = new ContentDialog - { - Title = "Couldn't connect to MPD server", - Content = "Please open Stylophone and configure a MPD server.", - CloseButtonText = "OK" - }; - await msgbox.ShowAsync(); + var dlg = new MessageDialog("Please open Stylophone and configure a MPD server.", "Couldn't connect to MPD server"); + await dlg.ShowAsync(); return; } From f44bd0c38cc5e7ba3885892dbc54502eb6fe9a68 Mon Sep 17 00:00:00 2001 From: Difegue Date: Wed, 4 Sep 2024 18:13:48 +0200 Subject: [PATCH 09/25] Cleanup protocol activation --- Sources/Stylophone/Activation/ProtocolActivationHandler.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Sources/Stylophone/Activation/ProtocolActivationHandler.cs b/Sources/Stylophone/Activation/ProtocolActivationHandler.cs index 0250415..960d714 100644 --- a/Sources/Stylophone/Activation/ProtocolActivationHandler.cs +++ b/Sources/Stylophone/Activation/ProtocolActivationHandler.cs @@ -1,18 +1,13 @@ using System; using System.Threading.Tasks; -using Stylophone.Services; using Stylophone.Common.Interfaces; using Windows.ApplicationModel.Activation; using CommunityToolkit.Mvvm.DependencyInjection; using Stylophone.Common.Services; -using Windows.UI.Xaml.Controls; using MpcNET.Commands.Playback; using MpcNET.Commands.Status; using MpcNET.Commands.Playlist; -using System.Xml.Linq; using Stylophone.Common.ViewModels; -using Windows.Services.Maps; -using System.Diagnostics; using Windows.UI.Popups; namespace Stylophone.Activation @@ -83,7 +78,7 @@ protected override async Task HandleInternalAsync(IActivatedEventArgs args) await _mpdService.SafelySendCommandAsync(new SetVolumeCommand((byte)(int.Parse(volume)))); break; case "stylophone_seek": - var seek = int.Parse(queryDictionary["volseekume"] ?? "0"); + var seek = int.Parse(queryDictionary["seek"] ?? "0"); await _mpdService.SafelySendCommandAsync(new SeekCurCommand(seek)); break; case "stylophone_load_playlist": From d9756d2fd47d64a2fc999f628ea23faf4a17ba8c Mon Sep 17 00:00:00 2001 From: Difegue Date: Wed, 4 Sep 2024 18:58:21 +0200 Subject: [PATCH 10/25] Update nugets --- .../Stylophone.Common.csproj | 12 +++---- Sources/Stylophone.iOS/Stylophone.iOS.csproj | 12 +++---- Sources/Stylophone/Stylophone.csproj | 33 +++++++++---------- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/Sources/Stylophone.Common/Stylophone.Common.csproj b/Sources/Stylophone.Common/Stylophone.Common.csproj index 0c2ffb0..269cef3 100644 --- a/Sources/Stylophone.Common/Stylophone.Common.csproj +++ b/Sources/Stylophone.Common/Stylophone.Common.csproj @@ -7,13 +7,13 @@ - + - - - - - + + + + + diff --git a/Sources/Stylophone.iOS/Stylophone.iOS.csproj b/Sources/Stylophone.iOS/Stylophone.iOS.csproj index 3d7d41f..ba3a929 100644 --- a/Sources/Stylophone.iOS/Stylophone.iOS.csproj +++ b/Sources/Stylophone.iOS/Stylophone.iOS.csproj @@ -60,13 +60,13 @@ - - - - - + + + + + - + diff --git a/Sources/Stylophone/Stylophone.csproj b/Sources/Stylophone/Stylophone.csproj index 30d1dae..1e71ff0 100644 --- a/Sources/Stylophone/Stylophone.csproj +++ b/Sources/Stylophone/Stylophone.csproj @@ -139,31 +139,30 @@ PackageReference - - - + + + 6.2.14 - 10.1901.28001 + 10.2307.3001 - - - - - - - - - - - + + + + + + + + + + - - + + 1.1.0 From b7d157fcf1bae6c72cd0cdbc7d1a66177b5c6136 Mon Sep 17 00:00:00 2001 From: Difegue Date: Wed, 4 Sep 2024 19:15:45 +0200 Subject: [PATCH 11/25] (#94) Make local playback port configurable instead of hardcoding 8000 + Windows UI --- .../ViewModels/LocalPlaybackViewModel.cs | 9 ++++++-- .../ViewModels/SettingsViewModel.cs | 9 ++++++++ .../Strings/Resources.Designer.cs | 18 +++++++++++++++ .../Strings/Resources.en-US.resx | 6 +++++ .../Strings/Resources.fr-FR.resx | 6 +++++ .../Strings/Resources.pt-PT.resx | 6 +++++ .../Strings/Resources.resx | 6 +++++ .../Activation/DefaultActivationHandler.cs | 3 ++- Sources/Stylophone/Views/SettingsPage.xaml | 23 +++++++++++++++---- 9 files changed, 79 insertions(+), 7 deletions(-) diff --git a/Sources/Stylophone.Common/ViewModels/LocalPlaybackViewModel.cs b/Sources/Stylophone.Common/ViewModels/LocalPlaybackViewModel.cs index 4b07ced..5fefbec 100644 --- a/Sources/Stylophone.Common/ViewModels/LocalPlaybackViewModel.cs +++ b/Sources/Stylophone.Common/ViewModels/LocalPlaybackViewModel.cs @@ -19,6 +19,7 @@ public partial class LocalPlaybackViewModel : ViewModelBase private LibVLC _vlcCore; private MediaPlayer _mediaPlayer; private string _serverHost; + private int _serverPort; public LocalPlaybackViewModel(SettingsViewModel settingsVm, MPDConnectionService mpdService, IInteropService interopService, INotificationService notificationService, IDispatcherService dispatcherService) : base(dispatcherService) { @@ -37,6 +38,9 @@ public LocalPlaybackViewModel(SettingsViewModel settingsVm, MPDConnectionService if (e.PropertyName == nameof(_settingsVm.ServerHost)) _serverHost = _settingsVm.ServerHost; + + if (e.PropertyName == nameof(_settingsVm.LocalPlaybackPort)) + _serverPort = _settingsVm.LocalPlaybackPort; }; // Run an idle loop in a spare thread to make sure the libVLC volume is always accurate @@ -57,9 +61,10 @@ public LocalPlaybackViewModel(SettingsViewModel settingsVm, MPDConnectionService }); } - public void Initialize(string host, bool isEnabled) + public void Initialize(string host, int port, bool isEnabled) { _serverHost = host; + _serverPort = port; IsEnabled = isEnabled; } @@ -147,7 +152,7 @@ partial void OnIsPlayingChanged(bool value) { if (value && _serverHost != null && _mpdService.IsConnected) { - var urlString = "http://" + _serverHost + ":8000"; + var urlString = "http://" + _serverHost + ":" + _serverPort; var streamUrl = new Uri(urlString); var media = new Media(_vlcCore, streamUrl); diff --git a/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs b/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs index 85f2852..99110d1 100644 --- a/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs +++ b/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs @@ -75,6 +75,9 @@ public SettingsViewModel(MPDConnectionService mpdService, IApplicationStorageSer [ObservableProperty] private bool _isLocalPlaybackEnabled; + [ObservableProperty] + private int _localPlaybackPort; + partial void OnElementThemeChanged(Theme value) { Task.Run (async () => await _interop.SetThemeAsync(value)); @@ -123,6 +126,11 @@ partial void OnIsLocalPlaybackEnabledChanged(bool value) _applicationStorageService.SetValue(nameof(IsLocalPlaybackEnabled), value); } + partial void OnLocalPlaybackPortChanged(int value) + { + _applicationStorageService.SetValue(nameof(LocalPlaybackPort), value); + } + [RelayCommand] private async Task ClearCacheAsync() @@ -176,6 +184,7 @@ public async Task EnsureInstanceInitializedAsync() _enableAnalytics = _applicationStorageService.GetValue(nameof(EnableAnalytics), true); _isAlbumArtFetchingEnabled = _applicationStorageService.GetValue(nameof(IsAlbumArtFetchingEnabled), true); _isLocalPlaybackEnabled = _applicationStorageService.GetValue(nameof(IsLocalPlaybackEnabled)); + _localPlaybackPort = _applicationStorageService.GetValue(nameof(LocalPlaybackPort), 8000); Enum.TryParse(_applicationStorageService.GetValue(nameof(ElementTheme)), out _elementTheme); diff --git a/Sources/Stylophone.Localization/Strings/Resources.Designer.cs b/Sources/Stylophone.Localization/Strings/Resources.Designer.cs index dd3195d..4f1c5b5 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.Designer.cs +++ b/Sources/Stylophone.Localization/Strings/Resources.Designer.cs @@ -974,6 +974,24 @@ public static string SettingsLocalPlaybackHeader { } } + /// + /// Looks up a localized string similar to Stream port. + /// + public static string SettingsLocalPlaybackPortHeader { + get { + return ResourceManager.GetString("SettingsLocalPlaybackPortHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Set this to the port of your server's httpd stream.. + /// + public static string SettingsLocalPlaybackPortText { + get { + return ResourceManager.GetString("SettingsLocalPlaybackPortText", resourceCulture); + } + } + /// /// Looks up a localized string similar to Stylophone can play your MPD Server's music stream. ///Enabling this option will show a second volume slider to control local volume.. diff --git a/Sources/Stylophone.Localization/Strings/Resources.en-US.resx b/Sources/Stylophone.Localization/Strings/Resources.en-US.resx index 50ac531..ed3ea60 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.en-US.resx +++ b/Sources/Stylophone.Localization/Strings/Resources.en-US.resx @@ -488,4 +488,10 @@ Enabling this option will show a second volume slider to control local volume. Song playback + + Stream port + + + Set this to the port of your server's httpd stream. + \ No newline at end of file diff --git a/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx b/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx index 2820b28..399c572 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx +++ b/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx @@ -487,4 +487,10 @@ L'activation de cette option affichera un second slider pour contrôler le volum Lecture de la piste + + Port du stream + + + Ajustez ce paramètre pour correspondre au port de votre stream httpd sur le serveur. + \ No newline at end of file diff --git a/Sources/Stylophone.Localization/Strings/Resources.pt-PT.resx b/Sources/Stylophone.Localization/Strings/Resources.pt-PT.resx index 135d070..ba164ff 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.pt-PT.resx +++ b/Sources/Stylophone.Localization/Strings/Resources.pt-PT.resx @@ -484,4 +484,10 @@ Ativando esta opção mostrará um segundo deslizador de volume para controlar o Leitura da faixa + + Porta do fluxo + + + Introduze a porta do fluxo httpd do seu servidor MPD aqui. + \ No newline at end of file diff --git a/Sources/Stylophone.Localization/Strings/Resources.resx b/Sources/Stylophone.Localization/Strings/Resources.resx index b18cecb..7c6bdd5 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.resx +++ b/Sources/Stylophone.Localization/Strings/Resources.resx @@ -488,4 +488,10 @@ Enabling this option will show a second volume slider to control local volume. Song playback + + Stream port + + + Set this to the port of your server's httpd stream. + \ No newline at end of file diff --git a/Sources/Stylophone/Activation/DefaultActivationHandler.cs b/Sources/Stylophone/Activation/DefaultActivationHandler.cs index 76e2f49..5959160 100644 --- a/Sources/Stylophone/Activation/DefaultActivationHandler.cs +++ b/Sources/Stylophone/Activation/DefaultActivationHandler.cs @@ -62,9 +62,10 @@ private async Task StartupAsync() var port = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerPort), 6600); var pass = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.ServerPassword)); var localPlaybackEnabled = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.IsLocalPlaybackEnabled)); + var localPlaybackPort = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.LocalPlaybackPort), 8000); var localPlaybackVm = Ioc.Default.GetRequiredService(); - localPlaybackVm.Initialize(host, localPlaybackEnabled); + localPlaybackVm.Initialize(host, localPlaybackPort, localPlaybackEnabled); var mpdService = Ioc.Default.GetRequiredService(); mpdService.SetServerInfo(host, port, pass); diff --git a/Sources/Stylophone/Views/SettingsPage.xaml b/Sources/Stylophone/Views/SettingsPage.xaml index 0e4057c..44220db 100644 --- a/Sources/Stylophone/Views/SettingsPage.xaml +++ b/Sources/Stylophone/Views/SettingsPage.xaml @@ -84,19 +84,34 @@ - - + - + - + + + + + + + From 9611a953423b13f69712d43d42f6c537ee82eada Mon Sep 17 00:00:00 2001 From: Difegue Date: Wed, 4 Sep 2024 19:20:27 +0200 Subject: [PATCH 12/25] (#99) Refine albumart loading error handling --- .../Services/AlbumArtService.cs | 22 ++++++++++++++----- Sources/Stylophone/Views/SettingsPage.xaml | 4 ++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Sources/Stylophone.Common/Services/AlbumArtService.cs b/Sources/Stylophone.Common/Services/AlbumArtService.cs index 0866df0..5f627e5 100644 --- a/Sources/Stylophone.Common/Services/AlbumArtService.cs +++ b/Sources/Stylophone.Common/Services/AlbumArtService.cs @@ -246,14 +246,26 @@ internal async Task LoadImageFromFile(string fileName) private SKBitmap ImageFromBytes(byte[] bytes) { - SKBitmap image = SKBitmap.Decode(bytes); + try + { + SKBitmap image = SKBitmap.Decode(bytes); - // Resize overly large images to reduce OOM risk. Is 2048 too small ? - if (image.Width > 2048) + if (image == null) + return null; + + // Resize overly large images to reduce OOM risk. Is 2048 too small ? + if (image.Width > 2048) + { + image.Resize(new SKImageInfo(2048, 2048 * image.Height / image.Width), SKFilterQuality.High); + } + return image; + } + catch (Exception e) { - image.Resize(new SKImageInfo(2048, 2048 * image.Height / image.Width), SKFilterQuality.High); + Debug.WriteLine("Exception caught while loading albumart from MPD response: " + e); + _notificationService.ShowErrorNotification(e); + return null; } - return image; } } diff --git a/Sources/Stylophone/Views/SettingsPage.xaml b/Sources/Stylophone/Views/SettingsPage.xaml index 44220db..0be3e18 100644 --- a/Sources/Stylophone/Views/SettingsPage.xaml +++ b/Sources/Stylophone/Views/SettingsPage.xaml @@ -97,7 +97,7 @@ OffContent="" OnContent="" /> - + @@ -110,7 +110,7 @@ SpinButtonPlacementMode="Compact" Value="{x:Bind ViewModel.LocalPlaybackPort, Mode=TwoWay}" /> - + From 1ee2b2aac8310ba36a22ca959cf47f10f7b68192 Mon Sep 17 00:00:00 2001 From: Difegue Date: Wed, 4 Sep 2024 19:25:17 +0200 Subject: [PATCH 13/25] (#98) Fix windows crash when searching for albums too fast --- Sources/Stylophone/ViewModels/LibraryViewModel.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/Stylophone/ViewModels/LibraryViewModel.cs b/Sources/Stylophone/ViewModels/LibraryViewModel.cs index c4360c7..22598a5 100644 --- a/Sources/Stylophone/ViewModels/LibraryViewModel.cs +++ b/Sources/Stylophone/ViewModels/LibraryViewModel.cs @@ -62,6 +62,12 @@ public void RangesChanged(ItemIndexRange visibleRange, IReadOnlyList= this.Count) + { + // The collection changed while this task was queued up, give up + return; + } + var album = this[i]; await album.LoadAlbumArtFromCacheAsync(); } From 6de8829f87f80036b7a802e90b56b60626602267 Mon Sep 17 00:00:00 2001 From: Difegue Date: Thu, 5 Sep 2024 02:17:20 +0200 Subject: [PATCH 14/25] (#91) Add support for toggling server outputs in settings --- .../ViewModels/Items/OutputViewModel.cs | 42 +++++++++++++++++++ .../ViewModels/SettingsViewModel.cs | 15 +++++-- .../Strings/Resources.Designer.cs | 18 ++++++++ .../Strings/Resources.en-US.resx | 6 +++ .../Strings/Resources.fr-FR.resx | 6 +++ .../Strings/Resources.pt-PT.resx | 6 +++ .../Strings/Resources.resx | 6 +++ Sources/Stylophone/Views/SettingsPage.xaml | 27 +++++++++++- 8 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 Sources/Stylophone.Common/ViewModels/Items/OutputViewModel.cs diff --git a/Sources/Stylophone.Common/ViewModels/Items/OutputViewModel.cs b/Sources/Stylophone.Common/ViewModels/Items/OutputViewModel.cs new file mode 100644 index 0000000..93413a7 --- /dev/null +++ b/Sources/Stylophone.Common/ViewModels/Items/OutputViewModel.cs @@ -0,0 +1,42 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.DependencyInjection; +using MpcNET; +using MpcNET.Commands.Output; +using MpcNET.Types; +using Stylophone.Common.Interfaces; +using Stylophone.Common.Services; + +namespace Stylophone.Common.ViewModels.Items +{ + public partial class OutputViewModel: ObservableObject + { + [ObservableProperty] + private string _name; + + [ObservableProperty] + private string _plugin; + + [ObservableProperty] + private bool _isEnabled; + + private int _id; + + public OutputViewModel() { } + + public OutputViewModel(MpdOutput o) + { + _id = o.Id; + _name = o.Name; + _plugin = o.Plugin; + _isEnabled = o.IsEnabled; + } + + partial void OnIsEnabledChanged(bool value) + { + IMpcCommand command = value ? new EnableOutputCommand(_id) : new DisableOutputCommand(_id); + + Ioc.Default.GetRequiredService().SafelySendCommandAsync(command); + } + + } +} diff --git a/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs b/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs index 99110d1..caabe67 100644 --- a/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs +++ b/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -7,8 +9,10 @@ using CommunityToolkit.Mvvm.Input; using MpcNET.Commands.Output; using MpcNET.Commands.Status; +using MpcNET.Types; using Stylophone.Common.Interfaces; using Stylophone.Common.Services; +using Stylophone.Common.ViewModels.Items; using Stylophone.Localization.Strings; namespace Stylophone.Common.ViewModels @@ -78,6 +82,9 @@ public SettingsViewModel(MPDConnectionService mpdService, IApplicationStorageSer [ObservableProperty] private int _localPlaybackPort; + [ObservableProperty] + private ObservableCollection _outputs; + partial void OnElementThemeChanged(Theme value) { Task.Run (async () => await _interop.SetThemeAsync(value)); @@ -241,13 +248,15 @@ private async Task UpdateServerVersionAsync() lastUpdatedDb = DateTimeOffset.FromUnixTimeSeconds(db_update).UtcDateTime; } - // Build info string + // Get server outputs var outputs = await _mpdService.SafelySendCommandAsync(new OutputsCommand()); + Outputs = new ObservableCollection(outputs.Select(o => new OutputViewModel(o))); var songs = response.ContainsKey("songs") ? response["songs"] : "??"; var albums = response.ContainsKey("albums") ? response["albums"] : "??"; - if (outputs != null && outputs.Count() > 0) + // Build info string + if (outputs?.Count() > 0) { var outputString = outputs.Select(o => o.Plugin).Aggregate((s, s2) => $"{s}, {s2}"); @@ -256,7 +265,7 @@ private async Task UpdateServerVersionAsync() $"Database last updated {lastUpdatedDb}\n" + $"Outputs available: {outputString}"; - IsStreamingAvailable = outputs.Select(o => o.Plugin).Contains("httpd"); + IsStreamingAvailable = outputs.Any(o => o.Plugin.Contains("httpd")); if (!IsStreamingAvailable) IsLocalPlaybackEnabled = false; diff --git a/Sources/Stylophone.Localization/Strings/Resources.Designer.cs b/Sources/Stylophone.Localization/Strings/Resources.Designer.cs index 4f1c5b5..6d5feeb 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.Designer.cs +++ b/Sources/Stylophone.Localization/Strings/Resources.Designer.cs @@ -1011,6 +1011,24 @@ public static string SettingsNoServerError { } } + /// + /// Looks up a localized string similar to Server Outputs. + /// + public static string SettingsOutputsHeader { + get { + return ResourceManager.GetString("SettingsOutputsHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable or disable the server's music outputs. . + /// + public static string SettingsOutputsText { + get { + return ResourceManager.GetString("SettingsOutputsText", resourceCulture); + } + } + /// /// Looks up a localized string similar to MPD Server. /// diff --git a/Sources/Stylophone.Localization/Strings/Resources.en-US.resx b/Sources/Stylophone.Localization/Strings/Resources.en-US.resx index ed3ea60..f208923 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.en-US.resx +++ b/Sources/Stylophone.Localization/Strings/Resources.en-US.resx @@ -494,4 +494,10 @@ Enabling this option will show a second volume slider to control local volume. Set this to the port of your server's httpd stream. + + Server Outputs + + + Enable or disable the server's music outputs. + \ No newline at end of file diff --git a/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx b/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx index 399c572..2e00502 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx +++ b/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx @@ -493,4 +493,10 @@ L'activation de cette option affichera un second slider pour contrôler le volum Ajustez ce paramètre pour correspondre au port de votre stream httpd sur le serveur. + + Sorties du serveur + + + Activez ou désactivez les sorties audio du serveur. + \ No newline at end of file diff --git a/Sources/Stylophone.Localization/Strings/Resources.pt-PT.resx b/Sources/Stylophone.Localization/Strings/Resources.pt-PT.resx index ba164ff..4ca659d 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.pt-PT.resx +++ b/Sources/Stylophone.Localization/Strings/Resources.pt-PT.resx @@ -490,4 +490,10 @@ Ativando esta opção mostrará um segundo deslizador de volume para controlar o Introduze a porta do fluxo httpd do seu servidor MPD aqui. + + Saidas do servidor + + + Active o deactive as saidas audio do servidor MPD. + \ No newline at end of file diff --git a/Sources/Stylophone.Localization/Strings/Resources.resx b/Sources/Stylophone.Localization/Strings/Resources.resx index 7c6bdd5..b0f2288 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.resx +++ b/Sources/Stylophone.Localization/Strings/Resources.resx @@ -494,4 +494,10 @@ Enabling this option will show a second volume slider to control local volume. Set this to the port of your server's httpd stream. + + Server Outputs + + + Enable or disable the server's music outputs. + \ No newline at end of file diff --git a/Sources/Stylophone/Views/SettingsPage.xaml b/Sources/Stylophone/Views/SettingsPage.xaml index 0be3e18..2d90199 100644 --- a/Sources/Stylophone/Views/SettingsPage.xaml +++ b/Sources/Stylophone/Views/SettingsPage.xaml @@ -10,6 +10,7 @@ xmlns:strings="using:Stylophone.Localization.Strings" xmlns:helpers="using:Stylophone.Helpers" xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" + xmlns:vm="using:Stylophone.Common.ViewModels.Items" mc:Ignorable="d"> @@ -98,7 +99,7 @@ OnContent="" /> - + + + + + + + + + + + + + + + Date: Thu, 5 Sep 2024 02:30:17 +0200 Subject: [PATCH 15/25] Bump version --- Sources/Stylophone.iOS/Info.plist | 4 ++-- Sources/Stylophone/Package.appxmanifest | 10 +++++----- Sources/Stylophone/Package.tt | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Stylophone.iOS/Info.plist b/Sources/Stylophone.iOS/Info.plist index b50512f..cd0472b 100644 --- a/Sources/Stylophone.iOS/Info.plist +++ b/Sources/Stylophone.iOS/Info.plist @@ -16,9 +16,9 @@ zh CFBundleShortVersionString - 2.6.0 + 2.7.0 CFBundleVersion - 2.6.0.2 + 2.7.0.0 LSRequiresIPhoneOS MinimumOSVersion diff --git a/Sources/Stylophone/Package.appxmanifest b/Sources/Stylophone/Package.appxmanifest index eda0563..980da86 100644 --- a/Sources/Stylophone/Package.appxmanifest +++ b/Sources/Stylophone/Package.appxmanifest @@ -9,15 +9,15 @@ xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" IgnorableNamespaces="uap mp genTemplate uap3"> - + Version="2.7.0.0" /> - Stylophone (Dev) + Stylophone Difegue Assets\StoreLogo.png @@ -35,7 +35,7 @@ Executable="$targetnametoken$.exe" EntryPoint="Stylophone.App"> <#@ parameter type="System.String" name="BuildConfiguration" #> <# - string version = "2.6.2.0"; + string version = "2.7.0.0"; // Get configuration name at Build time string configName = Host.ResolveParameterValue("-", "-", "BuildConfiguration"); From de6fbc9135c6ea8e210fca23cf0d8d468dd1d9c0 Mon Sep 17 00:00:00 2001 From: Difegue Date: Sun, 8 Sep 2024 03:09:08 +0200 Subject: [PATCH 16/25] Fix iOS build and cleanup --- Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs | 8 +++++--- .../Stylophone.Localization/Strings/Resources.fr-FR.resx | 2 +- Sources/Stylophone.iOS/AppDelegate.cs | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs b/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs index caabe67..e43a85b 100644 --- a/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs +++ b/Sources/Stylophone.Common/ViewModels/SettingsViewModel.cs @@ -83,7 +83,7 @@ public SettingsViewModel(MPDConnectionService mpdService, IApplicationStorageSer private int _localPlaybackPort; [ObservableProperty] - private ObservableCollection _outputs; + private ObservableCollection _outputs = new(); partial void OnElementThemeChanged(Theme value) { @@ -215,7 +215,6 @@ private async Task CheckUpdatingDbAsync() private string GetVersionDescription() { - var appName = Resources.AppDisplayName; Version version = _interop.GetAppVersion(); return $"{version.Major}.{version.Minor}.{(version.Revision > -1 ? version.Revision : 0)}"; @@ -250,7 +249,10 @@ private async Task UpdateServerVersionAsync() // Get server outputs var outputs = await _mpdService.SafelySendCommandAsync(new OutputsCommand()); - Outputs = new ObservableCollection(outputs.Select(o => new OutputViewModel(o))); + Outputs.Clear(); + + foreach (var o in outputs) + Outputs.Add(new OutputViewModel(o)); var songs = response.ContainsKey("songs") ? response["songs"] : "??"; var albums = response.ContainsKey("albums") ? response["albums"] : "??"; diff --git a/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx b/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx index 2e00502..4b67747 100644 --- a/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx +++ b/Sources/Stylophone.Localization/Strings/Resources.fr-FR.resx @@ -464,7 +464,7 @@ L'activation de cette option affichera un second slider pour contrôler le volum David Bowie is credited with playing the Stylophone on his 1969 debut hit song "Space Oddity" and also for his 2002 album Heathen track titled "Slip Away," as well as on the song "Heathen (The Rays)". - Récupérer les pochettes d'album depuis le serveur MPD + Télécharger les pochettes d'album depuis le serveur MPD Stylophone stocke les pochettes d'album sur votre ordinateur pour éviter de surcharger votre serveur MPD. diff --git a/Sources/Stylophone.iOS/AppDelegate.cs b/Sources/Stylophone.iOS/AppDelegate.cs index 1d4d465..b9cf07d 100644 --- a/Sources/Stylophone.iOS/AppDelegate.cs +++ b/Sources/Stylophone.iOS/AppDelegate.cs @@ -89,10 +89,11 @@ private async Task InitializeApplicationAsync() var host = storageService.GetValue(nameof(SettingsViewModel.ServerHost)); var port = storageService.GetValue(nameof(SettingsViewModel.ServerPort), 6600); var pass = storageService.GetValue(nameof(SettingsViewModel.ServerPassword)); - var localPlaybackEnabled = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.IsLocalPlaybackEnabled)); + var localPlaybackEnabled = Ioc.Default.GetRequiredService().GetValue(nameof(SettingsViewModel.IsLocalPlaybackEnabled)); + var localPlaybackPort = storageService.GetValue(nameof(SettingsViewModel.LocalPlaybackPort), 8000); var localPlaybackVm = Ioc.Default.GetRequiredService(); - localPlaybackVm.Initialize(host, localPlaybackEnabled); + localPlaybackVm.Initialize(host, localPlaybackPort, localPlaybackEnabled); var mpdService = Ioc.Default.GetRequiredService(); mpdService.SetServerInfo(host, port, pass); From 337e83cff200aff0c05f849a8e1b9c9aa822a2c4 Mon Sep 17 00:00:00 2001 From: Difegue Date: Sun, 8 Sep 2024 04:33:42 +0200 Subject: [PATCH 17/25] Implement server outputs/local playback port on iOS --- Sources/Stylophone.iOS/Stylophone.iOS.csproj | 3 + .../ViewControllers/SettingsViewController.cs | 98 +++++++- .../SettingsViewController.designer.cs | 26 +- .../SubViews/ServerOutputCell.cs | 39 +++ .../SubViews/ServerOutputCell.designer.cs | 34 +++ .../Stylophone.iOS/Views/Settings.storyboard | 229 +++++++++++++----- 6 files changed, 355 insertions(+), 74 deletions(-) create mode 100644 Sources/Stylophone.iOS/ViewControllers/SubViews/ServerOutputCell.cs create mode 100644 Sources/Stylophone.iOS/ViewControllers/SubViews/ServerOutputCell.designer.cs diff --git a/Sources/Stylophone.iOS/Stylophone.iOS.csproj b/Sources/Stylophone.iOS/Stylophone.iOS.csproj index ba3a929..95748e9 100644 --- a/Sources/Stylophone.iOS/Stylophone.iOS.csproj +++ b/Sources/Stylophone.iOS/Stylophone.iOS.csproj @@ -58,6 +58,9 @@ SymbolUIButton.cs + + ServerOutputCell.cs + diff --git a/Sources/Stylophone.iOS/ViewControllers/SettingsViewController.cs b/Sources/Stylophone.iOS/ViewControllers/SettingsViewController.cs index b81da0e..bcc5214 100644 --- a/Sources/Stylophone.iOS/ViewControllers/SettingsViewController.cs +++ b/Sources/Stylophone.iOS/ViewControllers/SettingsViewController.cs @@ -8,13 +8,17 @@ using Stylophone.iOS.Helpers; using Stylophone.Localization.Strings; using UIKit; +using System.Collections.ObjectModel; +using Stylophone.Common.ViewModels.Items; +using System.Collections.Specialized; +using Stylophone.iOS.Views; namespace Stylophone.iOS.ViewControllers { public partial class SettingsViewController : UITableViewController, IViewController { - public SettingsViewController(IntPtr handle) : base(handle) + public SettingsViewController(ObjCRuntime.NativeHandle handle) : base(handle) { } @@ -38,9 +42,10 @@ public override string TitleForHeader(UITableView tableView, nint section) { 0 => Resources.SettingsServer, 1 => Resources.SettingsLocalPlaybackHeader, - 2 => Resources.SettingsDatabase, - 3 => Resources.SettingsAnalytics, - 4 => Resources.SettingsAbout, + 2 => Resources.SettingsOutputsHeader, + 3 => Resources.SettingsDatabase, + 4 => Resources.SettingsAnalytics, + 5 => Resources.SettingsAbout, _ => "", }; } @@ -50,8 +55,9 @@ public override string TitleForFooter(UITableView tableView, nint section) return (int)section switch { 1 => Resources.SettingsLocalPlaybackText, - 2 => Resources.SettingsAlbumArtText, - 3 => Resources.SettingsApplyOnRestart, + 2 => Resources.SettingsOutputsText, + 3 => Resources.SettingsAlbumArtText, + 4 => Resources.SettingsApplyOnRestart, _ => "", }; } @@ -85,6 +91,10 @@ public override void ViewDidLoad() Binder.Bind(LocalPlaybackToggle, "enabled", nameof(ViewModel.IsStreamingAvailable)); Binder.Bind(LocalPlaybackToggle, "on", nameof(ViewModel.IsLocalPlaybackEnabled), true); + + Binder.Bind(LocalPlaybackPortField, "text", nameof(ViewModel.LocalPlaybackPort), true, + valueTransformer: intToStringTransformer); + Binder.Bind(AnalyticsToggle, "on", nameof(ViewModel.EnableAnalytics), true); Binder.Bind(AlbumArtToggle, "on", nameof(ViewModel.IsAlbumArtFetchingEnabled), true); @@ -112,7 +122,83 @@ public override void ViewDidLoad() ViewModel.RetryConnection(); }; + var outputsDataSource = new ServerOutputsDataSource(ServerOutputsTable, ViewModel.Outputs); + + ServerOutputsTable.DataSource = outputsDataSource; + ServerOutputsTable.Delegate = outputsDataSource; + + } + } + + public class ServerOutputsDataSource : UITableViewDelegate, IUITableViewDataSource + { + private UITableView _tableView; + private ObservableCollection _sourceCollection; + + public ServerOutputsDataSource(UITableView tableView, ObservableCollection source) + { + _tableView = tableView; + _sourceCollection = source; + + _sourceCollection.CollectionChanged += (s, e) => UIApplication.SharedApplication.InvokeOnMainThread( + () => UpdateUITableView(e)); } + + public nint RowsInSection(UITableView tableView, nint section) + { + return _sourceCollection.Count; + } + + public UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) + { + var cell = tableView.DequeueReusableCell("outputCell") as ServerOutputCell; + + if (_sourceCollection.Count <= indexPath.Row) + return cell; // Safety check + + var outputViewModel = _sourceCollection[indexPath.Row]; + + cell.Configure(indexPath.Row, outputViewModel); + + return cell; + } + + // TODO make an extension of UITableView? + private void UpdateUITableView(NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Reset) + { + _tableView.ReloadData(); + } + else + { + _tableView.BeginUpdates(); + + //Build a NSIndexPath array that matches the changes from the ObservableCollection. + var indexPaths = new List(); + + if (e.Action == NotifyCollectionChangedAction.Add) + { + for (var i = e.NewStartingIndex; i < e.NewStartingIndex + e.NewItems.Count; i++) + indexPaths.Add(NSIndexPath.FromItemSection(i, 0)); + + _tableView.InsertRows(indexPaths.ToArray(), UITableViewRowAnimation.Left); + } + + if (e.Action == NotifyCollectionChangedAction.Remove) + { + var startIndex = e.OldStartingIndex; + + for (var i = startIndex; i < startIndex + e.OldItems.Count; i++) + indexPaths.Add(NSIndexPath.FromItemSection(i, 0)); + + _tableView.DeleteRows(indexPaths.ToArray(), UITableViewRowAnimation.Right); + } + + _tableView.EndUpdates(); + } + } + } } diff --git a/Sources/Stylophone.iOS/ViewControllers/SettingsViewController.designer.cs b/Sources/Stylophone.iOS/ViewControllers/SettingsViewController.designer.cs index da6b3a7..8913c1a 100644 --- a/Sources/Stylophone.iOS/ViewControllers/SettingsViewController.designer.cs +++ b/Sources/Stylophone.iOS/ViewControllers/SettingsViewController.designer.cs @@ -24,6 +24,9 @@ partial class SettingsViewController [Outlet] UIKit.UIButton GithubButton { get; set; } + [Outlet] + UIKit.UITextField LocalPlaybackPortField { get; set; } + [Outlet] UIKit.UISwitch LocalPlaybackToggle { get; set; } @@ -48,6 +51,9 @@ partial class SettingsViewController [Outlet] UIKit.UILabel ServerInfoLabel { get; set; } + [Outlet] + UIKit.UITableView ServerOutputsTable { get; set; } + [Outlet] UIKit.UITextField ServerPasswordField { get; set; } @@ -62,6 +68,11 @@ partial class SettingsViewController void ReleaseDesignerOutlets () { + if (AlbumArtToggle != null) { + AlbumArtToggle.Dispose (); + AlbumArtToggle = null; + } + if (AnalyticsToggle != null) { AnalyticsToggle.Dispose (); AnalyticsToggle = null; @@ -77,16 +88,16 @@ void ReleaseDesignerOutlets () GithubButton = null; } + if (LocalPlaybackPortField != null) { + LocalPlaybackPortField.Dispose (); + LocalPlaybackPortField = null; + } + if (LocalPlaybackToggle != null) { LocalPlaybackToggle.Dispose (); LocalPlaybackToggle = null; } - if (AlbumArtToggle != null) { - AlbumArtToggle.Dispose (); - AlbumArtToggle = null; - } - if (RateButton != null) { RateButton.Dispose (); RateButton = null; @@ -141,6 +152,11 @@ void ReleaseDesignerOutlets () VersionLabel.Dispose (); VersionLabel = null; } + + if (ServerOutputsTable != null) { + ServerOutputsTable.Dispose (); + ServerOutputsTable = null; + } } } } diff --git a/Sources/Stylophone.iOS/ViewControllers/SubViews/ServerOutputCell.cs b/Sources/Stylophone.iOS/ViewControllers/SubViews/ServerOutputCell.cs new file mode 100644 index 0000000..ef56a9c --- /dev/null +++ b/Sources/Stylophone.iOS/ViewControllers/SubViews/ServerOutputCell.cs @@ -0,0 +1,39 @@ +// This file has been autogenerated from a class added in the UI designer. + +using System; +using Stylophone.Common.Helpers; +using Stylophone.Common.ViewModels; +using Stylophone.iOS.Helpers; +using Foundation; +using UIKit; +using Stylophone.Common.ViewModels.Items; + +namespace Stylophone.iOS.Views +{ + public partial class ServerOutputCell : UITableViewCell + { + public ServerOutputCell (IntPtr handle) : base (handle) + { + } + + private PropertyBinder _propertyBinder; + private OutputViewModel _outputViewModel; + + internal void Configure(int row, OutputViewModel outputViewModel) + { + // Bind output + _outputViewModel = outputViewModel; + _propertyBinder?.Dispose(); + _propertyBinder = new PropertyBinder(outputViewModel); + var negateBoolTransformer = NSValueTransformer.GetValueTransformer(nameof(ReverseBoolValueTransformer)); + + if (outputViewModel != null) + { + OutputLabel.Text = outputViewModel.Name + " (" + outputViewModel.Plugin + ")"; + + _propertyBinder.Bind(OutputToggle, "on", nameof(outputViewModel.IsEnabled), true); + } + + } + } +} diff --git a/Sources/Stylophone.iOS/ViewControllers/SubViews/ServerOutputCell.designer.cs b/Sources/Stylophone.iOS/ViewControllers/SubViews/ServerOutputCell.designer.cs new file mode 100644 index 0000000..ae1b51b --- /dev/null +++ b/Sources/Stylophone.iOS/ViewControllers/SubViews/ServerOutputCell.designer.cs @@ -0,0 +1,34 @@ +// WARNING +// +// This file has been generated automatically by Visual Studio to store outlets and +// actions made in the UI designer. If it is removed, they will be lost. +// Manual changes to this file may not be handled correctly. +// +using Foundation; +using System.CodeDom.Compiler; + +namespace Stylophone.iOS.Views +{ + [Register ("ServerOutputCell")] + partial class ServerOutputCell + { + [Outlet] + UIKit.UILabel OutputLabel { get; set; } + + [Outlet] + Stylophone.iOS.Helpers.KvoUISwitch OutputToggle { get; set; } + + void ReleaseDesignerOutlets () + { + if (OutputLabel != null) { + OutputLabel.Dispose (); + OutputLabel = null; + } + + if (OutputToggle != null) { + OutputToggle.Dispose (); + OutputToggle = null; + } + } + } +} diff --git a/Sources/Stylophone.iOS/Views/Settings.storyboard b/Sources/Stylophone.iOS/Views/Settings.storyboard index 35f1c1b..07637a2 100644 --- a/Sources/Stylophone.iOS/Views/Settings.storyboard +++ b/Sources/Stylophone.iOS/Views/Settings.storyboard @@ -1,8 +1,9 @@ - - + + - + + @@ -13,24 +14,24 @@ - + - + - + - + - + @@ -38,7 +39,7 @@ - + - + - + @@ -95,14 +96,14 @@ - + - + - + @@ -131,26 +132,26 @@ - + - + - + - + @@ -159,7 +160,7 @@ - + @@ -191,17 +192,17 @@ - + - + - + - + @@ -227,19 +228,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + @@ -264,14 +362,14 @@ - + - + - @@ -284,14 +382,14 @@ - + - + - @@ -308,20 +406,20 @@ - + - + - - + + - - + + + + From 27aa2c4c79619d760222d32d095a7a212b87b28f Mon Sep 17 00:00:00 2001 From: Difegue Date: Sun, 8 Sep 2024 05:17:41 +0200 Subject: [PATCH 18/25] Absolute minimum effort for catalyst --- Sources/Stylophone.iOS/AppDelegate.cs | 16 ++++++ .../Stylophone.iOS/Views/Settings.storyboard | 54 +++++++++---------- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/Sources/Stylophone.iOS/AppDelegate.cs b/Sources/Stylophone.iOS/AppDelegate.cs index b9cf07d..9627698 100644 --- a/Sources/Stylophone.iOS/AppDelegate.cs +++ b/Sources/Stylophone.iOS/AppDelegate.cs @@ -82,6 +82,22 @@ public void OnActivated(UIApplication application) ApplicationWillBecomeActive?.Invoke(this, EventArgs.Empty); } + public override void BuildMenu(IUIMenuBuilder builder) + { + base.BuildMenu(builder); + + builder.RemoveMenu(UIMenuIdentifier.Edit.GetConstant()); + builder.RemoveMenu(UIMenuIdentifier.Font.GetConstant()); + builder.RemoveMenu(UIMenuIdentifier.Format.GetConstant()); + builder.RemoveMenu(UIMenuIdentifier.Services.GetConstant()); + builder.RemoveMenu(UIMenuIdentifier.Hide.GetConstant()); + + builder.RemoveMenu(UIMenuIdentifier.File.GetConstant()); + builder.RemoveMenu(UIMenuIdentifier.Document.GetConstant()); + + builder.System.SetNeedsRebuild(); + } + private async Task InitializeApplicationAsync() { var storageService = Ioc.Default.GetRequiredService(); diff --git a/Sources/Stylophone.iOS/Views/Settings.storyboard b/Sources/Stylophone.iOS/Views/Settings.storyboard index 07637a2..9de7127 100644 --- a/Sources/Stylophone.iOS/Views/Settings.storyboard +++ b/Sources/Stylophone.iOS/Views/Settings.storyboard @@ -17,7 +17,7 @@ - + @@ -268,18 +268,18 @@ - - + + - + - + - + @@ -330,14 +330,14 @@ - + - + - + @@ -362,14 +362,14 @@ - + - + - @@ -382,14 +382,14 @@ - + - + - @@ -406,20 +406,20 @@ - + - + - - + + -