Initial implementation of the UI.
This commit is contained in:
		
							parent
							
								
									7f19615830
								
							
						
					
					
						commit
						182e473d1b
					
				
					 37 changed files with 1468 additions and 9 deletions
				
			
		
							
								
								
									
										36
									
								
								.github/main.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								.github/main.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | name: CI/CD | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: | ||||||
|  |       - main | ||||||
|  |       - '*' | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   build: | ||||||
|  |     runs-on: windows-latest | ||||||
|  | 
 | ||||||
|  |     steps: | ||||||
|  |     - uses: actions/checkout@v4 | ||||||
|  | 
 | ||||||
|  |     - name: Setup .NET | ||||||
|  |       uses: actions/setup-dotnet@v4 | ||||||
|  |       with: | ||||||
|  |         dotnet-version: 8.0.x | ||||||
|  | 
 | ||||||
|  |     - name: Restore dependencies | ||||||
|  |       run: dotnet restore | ||||||
|  | 
 | ||||||
|  |     - name: Install tools | ||||||
|  |       run: dotnet tool install --global vpk | ||||||
|  |      | ||||||
|  |     - name: Dotnet publish | ||||||
|  |       run: dotnet publish src/Plpext/Plpext.UI/Plpext.UI.csproj -c Release -r win-x64 -o publish | ||||||
|  | 
 | ||||||
|  |     - name: Vpk pack | ||||||
|  |       run: vpk pack --mainExe=Plpext.exe --packId=Plpext --packVersion=1.0.0 --packDir=publish -o build/win | ||||||
|  |      | ||||||
|  |     - name: Upload artifact | ||||||
|  |       uses: softprops/action-gh-release@v2 | ||||||
|  |       with: | ||||||
|  |         files: build/win/Plpext-win-Setup.exe | ||||||
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -9,3 +9,4 @@ artifacts/ | ||||||
| *.userprefs | *.userprefs | ||||||
| *DS_Store | *DS_Store | ||||||
| *.sln.ide | *.sln.ide | ||||||
|  | /build/win | ||||||
|  |  | ||||||
|  | @ -4,14 +4,14 @@ | ||||||
|     <TargetFramework>net8.0</TargetFramework> |     <TargetFramework>net8.0</TargetFramework> | ||||||
|     <ImplicitUsings>enable</ImplicitUsings> |     <ImplicitUsings>enable</ImplicitUsings> | ||||||
|     <Nullable>enable</Nullable> |     <Nullable>enable</Nullable> | ||||||
|     <Platforms>x86;x64</Platforms> |     <Platforms>x86;x64;AnyCPU</Platforms> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <ProjectReference Include="..\Plpext.Core\Plpext.Core.csproj" /> |     <ProjectReference Include="..\Plpext.Core\Plpext.Core.csproj" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup Condition="'$(Platform)' == 'x86' Or '$(PlatformTarget)' == 'x86'"> |   <ItemGroup Condition="'$(Platform)' == 'x86' Or '$(PlatformTarget)' == 'x86' Or '$(Platform)' == 'AnyCPU' Or '$(PlatformTarget)' == 'AnyCPU' "> | ||||||
|     <None Include="lib\x86\OpenAL32.dll"> |     <None Include="lib\x86\OpenAL32.dll"> | ||||||
|       <CopyToOutputDirectory>Always</CopyToOutputDirectory> |       <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||||
|       <TargetPath>OpenAL32.dll</TargetPath> |       <TargetPath>OpenAL32.dll</TargetPath> | ||||||
|  |  | ||||||
|  | @ -61,7 +61,7 @@ namespace Plpext.Core.AudioConverter | ||||||
|              |              | ||||||
|             var audioDuration = (double)(pcmData.Length / 2) / mp3Stream.Frequency; |             var audioDuration = (double)(pcmData.Length / 2) / mp3Stream.Frequency; | ||||||
| 
 | 
 | ||||||
|             return new AudioFile() { Name = baseFile.Name, Data = pcmData, Duration = TimeSpan.FromSeconds(audioDuration), Format = AudioFormat.Mono16, Frequency = mp3Stream.Frequency }; |             return new AudioFile() { Name = baseFile.Name, MP3Data = file, Data = pcmData, Duration = TimeSpan.FromSeconds(audioDuration), Format = AudioFormat.Mono16, Frequency = mp3Stream.Frequency }; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private static byte[] ResampleToMono(Span<byte> data) |         private static byte[] ResampleToMono(Span<byte> data) | ||||||
|  |  | ||||||
|  | @ -26,11 +26,10 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable | ||||||
| 
 | 
 | ||||||
|     public PlaybackState State { get; private set; } = PlaybackState.Stopped; |     public PlaybackState State { get; private set; } = PlaybackState.Stopped; | ||||||
| 
 | 
 | ||||||
|     public async Task<bool> InitAudioPlayerAsync(AudioFile input, CancellationToken cancellationToken) |     public async Task<bool> InitAudioPlayerAsync(AudioFile input, bool autoStart, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         _audioFile = input; |         _audioFile = input; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         _playbackCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); |         _playbackCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|  | @ -42,7 +41,9 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable | ||||||
| 
 | 
 | ||||||
|             AL.BufferData(bufferId, ALFormat.Mono16, input.Data.Span, input.Frequency); |             AL.BufferData(bufferId, ALFormat.Mono16, input.Data.Span, input.Frequency); | ||||||
|             AL.Source(sourceId, ALSourcei.Buffer, bufferId); |             AL.Source(sourceId, ALSourcei.Buffer, bufferId); | ||||||
|  |             if(autoStart) | ||||||
|                 return await Start(); |                 return await Start(); | ||||||
|  |             return true; | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ namespace Plpext.Core.Interfaces; | ||||||
| 
 | 
 | ||||||
| public interface IAudioPlayer | public interface IAudioPlayer | ||||||
| { | { | ||||||
|     Task<bool> InitAudioPlayerAsync(AudioFile input, CancellationToken cancellationToken); |     Task<bool> InitAudioPlayerAsync(AudioFile input, bool autoStart, CancellationToken cancellationToken); | ||||||
|     void Resume(); |     void Resume(); | ||||||
|     void Pause(); |     void Pause(); | ||||||
|     void Stop(); |     void Stop(); | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ namespace Plpext.Core.Models | ||||||
|     public record AudioFile |     public record AudioFile | ||||||
|     { |     { | ||||||
|         public required string Name { get; init; } |         public required string Name { get; init; } | ||||||
|  |         public ReadOnlyMemory<byte> MP3Data { get; init; }  | ||||||
|         public ReadOnlyMemory<byte> Data { get; init; } |         public ReadOnlyMemory<byte> Data { get; init; } | ||||||
|         public TimeSpan Duration { get; init; } |         public TimeSpan Duration { get; init; } | ||||||
|         public AudioFormat Format { get; init; } |         public AudioFormat Format { get; init; } | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								src/Plpext/Plpext.UI/App.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/Plpext/Plpext.UI/App.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | <Application xmlns="https://github.com/avaloniaui" | ||||||
|  |              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||||||
|  |              x:Class="Plpext.UI.App" | ||||||
|  |              RequestedThemeVariant="Dark"> | ||||||
|  |               | ||||||
|  |     <Application.Resources> | ||||||
|  |         <ResourceDictionary> | ||||||
|  |             <ResourceDictionary.MergedDictionaries> | ||||||
|  |                 <ResourceInclude Source="avares://Plpext/Resources/Colors.axaml"/> | ||||||
|  |                 <ResourceInclude Source="avares://Plpext/Controls/AudioPlayerControl.axaml"/> | ||||||
|  |             </ResourceDictionary.MergedDictionaries> | ||||||
|  |         </ResourceDictionary> | ||||||
|  |     </Application.Resources> | ||||||
|  |    | ||||||
|  |     <Application.Styles> | ||||||
|  |         <FluentTheme /> | ||||||
|  |         <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/> | ||||||
|  |         <StyleInclude Source="avares://Plpext/Styles/AudioPlayerControl.axaml"/> | ||||||
|  |     </Application.Styles> | ||||||
|  | </Application> | ||||||
							
								
								
									
										48
									
								
								src/Plpext/Plpext.UI/App.axaml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/Plpext/Plpext.UI/App.axaml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | using System; | ||||||
|  | using System.Linq; | ||||||
|  | using Avalonia; | ||||||
|  | using Avalonia.Controls; | ||||||
|  | using Avalonia.Controls.ApplicationLifetimes; | ||||||
|  | using Avalonia.Data.Core; | ||||||
|  | using Avalonia.Data.Core.Plugins; | ||||||
|  | using Avalonia.Markup.Xaml; | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Plpext.Core.AudioPlayer; | ||||||
|  | using Plpext.UI.DependencyInjection; | ||||||
|  | using Plpext.UI.ViewModels; | ||||||
|  | using Plpext.UI.Views; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI | ||||||
|  | { | ||||||
|  |     public class App : Application | ||||||
|  |     { | ||||||
|  |         public override void Initialize() | ||||||
|  |         { | ||||||
|  |             AvaloniaXamlLoader.Load(this); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public override void OnFrameworkInitializationCompleted() | ||||||
|  |         { | ||||||
|  |             if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) | ||||||
|  |             { | ||||||
|  |                 DisableAvaloniaDataAnnotationValidation(); | ||||||
|  |                 Container.Services.GetRequiredService<AudioContext>().Initialize(); | ||||||
|  |                 desktop.MainWindow = Container.Services.GetRequiredService<MainWindow>(); | ||||||
|  |                 desktop.MainWindow.DataContext = Container.Services.GetRequiredService<MainWindowViewModel>(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             base.OnFrameworkInitializationCompleted(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private void DisableAvaloniaDataAnnotationValidation() | ||||||
|  |         { | ||||||
|  |             var dataValidationPluginsToRemove = | ||||||
|  |                 BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray(); | ||||||
|  | 
 | ||||||
|  |             foreach (var plugin in dataValidationPluginsToRemove) | ||||||
|  |             { | ||||||
|  |                 BindingPlugins.DataValidators.Remove(plugin); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										82
									
								
								src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,82 @@ | ||||||
|  | <ResourceDictionary | ||||||
|  |     xmlns="https://github.com/avaloniaui" | ||||||
|  |     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||||||
|  |     xmlns:controls="using:Plpext.UI" | ||||||
|  |     xmlns:vm="clr-namespace:Plpext.UI.ViewModels" | ||||||
|  |     xmlns:cv="clr-namespace:Plpext.UI.Controls.Converters"> | ||||||
|  | 
 | ||||||
|  |     <!-- | ||||||
|  |         Additional resources | ||||||
|  |         Using Control Themes: | ||||||
|  |         https://docs.avaloniaui.net/docs/basics/user-interface/styling/control-themes | ||||||
|  |         Using Theme Variants: | ||||||
|  |         https://docs.avaloniaui.net/docs/guides/styles-and-resources/how-to-use-theme-variants | ||||||
|  |     --> | ||||||
|  | 
 | ||||||
|  |     <Design.PreviewWith> | ||||||
|  |         <Border Padding="10" Background="Brown"> | ||||||
|  |         <StackPanel Width="200" Spacing="10"> | ||||||
|  |             <StackPanel Background="{DynamicResource SystemRegionBrush}"> | ||||||
|  |                 <controls:AudioPlayerControl IsPlaying="True" PlaybackState="Stopped" /> | ||||||
|  |                 <controls:AudioPlayerControl IsPlaying="True" PlaybackState="Paused" /> | ||||||
|  |                 <controls:AudioPlayerControl IsPlaying="True" PlaybackState="Playing" /> | ||||||
|  |                 <controls:AudioPlayerControl IsPlaying="False" PlaybackState="Stopped" /> | ||||||
|  |             </StackPanel> | ||||||
|  |         </StackPanel> | ||||||
|  |         </Border> | ||||||
|  |     </Design.PreviewWith> | ||||||
|  | 
 | ||||||
|  |     <ControlTheme x:Key="{x:Type controls:AudioPlayerControl}" TargetType="controls:AudioPlayerControl" | ||||||
|  |                   x:DataType="vm:AudioPlayerViewModel"> | ||||||
|  |         <Setter Property="Template"> | ||||||
|  |             <ControlTemplate> | ||||||
|  |                 <Grid Name="ButtonGrid" ColumnDefinitions="*,*, Auto"> | ||||||
|  |                     <Grid.Transitions> | ||||||
|  |                         <Transitions> | ||||||
|  |                             <ThicknessTransition Property="Margin" Duration="0:0:0.3" Easing="SineEaseInOut"/> | ||||||
|  |                         </Transitions> | ||||||
|  |                     </Grid.Transitions> | ||||||
|  |                     <Button Name="PlayButton" Grid.Column="0" IsEnabled="{TemplateBinding IsEnabled}"  | ||||||
|  |                             HorizontalAlignment="Center" Width="32" Height="32"  | ||||||
|  |                             Padding="6 6 6 6" | ||||||
|  |                             CornerRadius="12" | ||||||
|  |                             FontSize="{TemplateBinding FontSize}"  | ||||||
|  |                             CommandParameter="{TemplateBinding PlayCommandParameter}" Command="{TemplateBinding PlayCommand}"> | ||||||
|  |                         <Path Fill="{Binding $parent[Button].Foreground}"> | ||||||
|  |                             <Path.Data> | ||||||
|  |                                 <Binding Path="PlaybackState"  | ||||||
|  |                                          RelativeSource="{RelativeSource TemplatedParent}" | ||||||
|  |                                          Converter="{x:Static cv:PlaybackStateToPathConverter.Instance}"/> | ||||||
|  |                             </Path.Data> | ||||||
|  |                         </Path> | ||||||
|  |                     </Button> | ||||||
|  |                     <Button Name="StopButton" Grid.Column="1" IsEnabled="{TemplateBinding IsEnabled}" IsVisible="{TemplateBinding IsPlaying}"  | ||||||
|  |                             HorizontalAlignment="Center" Width="32" Height="32"  | ||||||
|  |                             Padding="6 6 6 6" | ||||||
|  |                             CornerRadius="12" | ||||||
|  |                             FontSize="{TemplateBinding FontSize}"  | ||||||
|  |                             CommandParameter="{TemplateBinding StopCommandParameter}" Command="{TemplateBinding StopCommand}"> | ||||||
|  |                         <Path Data="M2,2 H14 V14 H2 Z" Fill="{Binding $parent[Button].Foreground}" /> | ||||||
|  |                     </Button> | ||||||
|  |                     <StackPanel Grid.Column="2" Name="ProgressPanel" Orientation="Vertical"> | ||||||
|  |                         <StackPanel.Transitions> | ||||||
|  |                             <Transitions> | ||||||
|  |                                 <DoubleTransition Property="Width" Duration="0:0:0.3" Easing="SineEaseInOut"/> | ||||||
|  |                                 <DoubleTransition Property="Opacity" Duration="0:0:0.3"/> | ||||||
|  |                             </Transitions> | ||||||
|  |                         </StackPanel.Transitions> | ||||||
|  |                         <ProgressBar Name="ProgressPanelBar" Value="{TemplateBinding Progress}" HorizontalAlignment="Left" CornerRadius="2"/> | ||||||
|  |                         <StackPanel Orientation="Horizontal"> | ||||||
|  |                             <Label FontSize="{TemplateBinding FontSize}"  Content="{TemplateBinding CurrentDuration}"/> | ||||||
|  |                             <Label FontSize="{TemplateBinding FontSize}"  Content="/"/> | ||||||
|  |                             <Label FontSize="{TemplateBinding FontSize}"  Content="{TemplateBinding TotalDuration}"/> | ||||||
|  |                         </StackPanel> | ||||||
|  |                     </StackPanel> | ||||||
|  |                 </Grid> | ||||||
|  |             </ControlTemplate> | ||||||
|  |         </Setter> | ||||||
|  |          | ||||||
|  |        | ||||||
|  |     </ControlTheme> | ||||||
|  |      | ||||||
|  | </ResourceDictionary> | ||||||
							
								
								
									
										107
									
								
								src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/Plpext/Plpext.UI/Controls/AudioPlayerControl.axaml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | ||||||
|  | using Avalonia; | ||||||
|  | using Avalonia.Controls; | ||||||
|  | using Avalonia.Controls.Primitives; | ||||||
|  | using System.Windows.Input; | ||||||
|  | using Avalonia.Data; | ||||||
|  | using Plpext.Core.Models; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI; | ||||||
|  | 
 | ||||||
|  | public class AudioPlayerControl : TemplatedControl | ||||||
|  | { | ||||||
|  |     public static readonly StyledProperty<PlaybackState> PlaybackStateProperty = | ||||||
|  |         AvaloniaProperty.Register<AudioPlayerControl, PlaybackState>(nameof(PlaybackState), defaultValue: PlaybackState.Unknown, inherits: false, defaultBindingMode: BindingMode.OneWay); | ||||||
|  | 
 | ||||||
|  |     public PlaybackState PlaybackState | ||||||
|  |     { | ||||||
|  |         get {return GetValue(PlaybackStateProperty);} | ||||||
|  |         set {SetValue(PlaybackStateProperty, value);} | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public static readonly StyledProperty<bool> IsPlayingProperty = | ||||||
|  |         AvaloniaProperty.Register<AudioPlayerControl, bool>(nameof(IsPlaying), false, false, Avalonia.Data.BindingMode.TwoWay); | ||||||
|  | 
 | ||||||
|  |     public bool IsPlaying | ||||||
|  |     { | ||||||
|  |         get { return GetValue(IsPlayingProperty); } | ||||||
|  |         set { SetValue(IsPlayingProperty, value); } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public static readonly StyledProperty<string> CurrentDurationProperty = AvaloniaProperty.Register<AudioPlayerControl, string> | ||||||
|  |     ( | ||||||
|  |         name: nameof(CurrentDuration), | ||||||
|  |         defaultValue: "0:55", | ||||||
|  |         inherits: false, | ||||||
|  |         defaultBindingMode: Avalonia.Data.BindingMode.TwoWay, | ||||||
|  |         validate: (x) => x != "0" | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     public string CurrentDuration | ||||||
|  |     { | ||||||
|  |         get { return GetValue(CurrentDurationProperty); } | ||||||
|  |         set { SetValue(CurrentDurationProperty, value); } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static readonly StyledProperty<string> TotalDurationProperty = AvaloniaProperty.Register<AudioPlayerControl, string> | ||||||
|  |     ( | ||||||
|  |         name: nameof(TotalDuration), | ||||||
|  |         defaultValue: "1:07", | ||||||
|  |         inherits: false, | ||||||
|  |         defaultBindingMode: Avalonia.Data.BindingMode.TwoWay, | ||||||
|  |         validate: (x) => x != "0" | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     public string TotalDuration | ||||||
|  |     { | ||||||
|  |         get { return GetValue(TotalDurationProperty); } | ||||||
|  |         set { SetValue(TotalDurationProperty, value); } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static readonly StyledProperty<double> ProgressProperty = AvaloniaProperty.Register<AudioPlayerControl, double> | ||||||
|  |     ( | ||||||
|  |         name: nameof(Progress), | ||||||
|  |         defaultValue: 0, | ||||||
|  |         inherits: false, | ||||||
|  |         defaultBindingMode: Avalonia.Data.BindingMode.TwoWay, | ||||||
|  |         validate: (x) => x >= 0 | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     public double Progress | ||||||
|  |     { | ||||||
|  |         get { return GetValue(ProgressProperty); } | ||||||
|  |         set { SetValue(ProgressProperty, value); } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static readonly StyledProperty<ICommand?> PlayCommandProperty = | ||||||
|  |             AvaloniaProperty.Register<Button, ICommand?>(nameof(PlayCommand)); | ||||||
|  |     public static readonly StyledProperty<object?> PlayCommandParameterProperty = | ||||||
|  | AvaloniaProperty.Register<Button, object?>(nameof(PlayCommandParameter)); | ||||||
|  | 
 | ||||||
|  |     public ICommand? PlayCommand | ||||||
|  |     { | ||||||
|  |         get => GetValue(PlayCommandProperty); | ||||||
|  |         set => SetValue(PlayCommandProperty, value); | ||||||
|  |     } | ||||||
|  |     public object? PlayCommandParameter | ||||||
|  |     { | ||||||
|  |         get => GetValue(PlayCommandParameterProperty); | ||||||
|  |         set => SetValue(PlayCommandParameterProperty, value); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public static readonly StyledProperty<ICommand?> StopCommandProperty = | ||||||
|  |         AvaloniaProperty.Register<Button, ICommand?>(nameof(StopCommand)); | ||||||
|  |     public static readonly StyledProperty<object?> StopCommandParameterProperty = | ||||||
|  |         AvaloniaProperty.Register<Button, object?>(nameof(StopCommandParameter)); | ||||||
|  | 
 | ||||||
|  |     public ICommand? StopCommand | ||||||
|  |     { | ||||||
|  |         get => GetValue(StopCommandProperty); | ||||||
|  |         set => SetValue(StopCommandProperty, value); | ||||||
|  |     } | ||||||
|  |     public object? StopCommandParameter | ||||||
|  |     { | ||||||
|  |         get => GetValue(StopCommandParameterProperty); | ||||||
|  |         set => SetValue(StopCommandParameterProperty, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | using System; | ||||||
|  | using System.Globalization; | ||||||
|  | using Avalonia.Data.Converters; | ||||||
|  | using Avalonia.Media; | ||||||
|  | using Plpext.Core.Models; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.Controls.Converters; | ||||||
|  | 
 | ||||||
|  | public class PlaybackStateToPathConverter : IValueConverter | ||||||
|  | { | ||||||
|  |     public static readonly PlaybackStateToPathConverter Instance = new(); | ||||||
|  | 
 | ||||||
|  |     public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) | ||||||
|  |     { | ||||||
|  |         if (value is PlaybackState state) | ||||||
|  |         { | ||||||
|  |             return state == PlaybackState.Playing | ||||||
|  |                 ? Geometry.Parse("M4,2 H7 V14 H4 Z M11,2 H14 V14 H11 Z") | ||||||
|  |                 : Geometry.Parse("M5,2 L5,14 L15,8 Z"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return Geometry.Parse("M4,2 L4,14 L14,8 Z"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) | ||||||
|  |     { | ||||||
|  |         throw new NotImplementedException(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										63
									
								
								src/Plpext/Plpext.UI/DependencyInjection/Container.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/Plpext/Plpext.UI/DependencyInjection/Container.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Microsoft.Extensions.Hosting; | ||||||
|  | using Plpext.Core.FileStorage; | ||||||
|  | using Plpext.Core.Interfaces; | ||||||
|  | using Plpext.Core.MP3Parser; | ||||||
|  | using Plpext.Core.PackExtractor; | ||||||
|  | using Plpext.UI.ViewModels; | ||||||
|  | using Serilog; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Plpext.Core.AudioConverter; | ||||||
|  | using Plpext.Core.AudioPlayer; | ||||||
|  | using Plpext.UI.Services; | ||||||
|  | using Plpext.UI.Services.Converter; | ||||||
|  | using Plpext.UI.Services.FileLoader; | ||||||
|  | using Plpext.UI.Services.PlatformStorage; | ||||||
|  | using Plpext.UI.Views; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.DependencyInjection | ||||||
|  | { | ||||||
|  |     public static class Container | ||||||
|  |     { | ||||||
|  |         private static IServiceProvider? _container; | ||||||
|  |         public static IServiceProvider Services | ||||||
|  |         { | ||||||
|  |             get => _container ?? Register(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static IServiceProvider Register() | ||||||
|  |         { | ||||||
|  |             var hostBuilder = Host | ||||||
|  |                 .CreateDefaultBuilder() | ||||||
|  |                 .UseSerilog((context, loggerConfiguration) => | ||||||
|  |                 { | ||||||
|  |                     loggerConfiguration.WriteTo.Debug(); | ||||||
|  |                 }) | ||||||
|  |                 .ConfigureServices((context, services) => | ||||||
|  |                 { | ||||||
|  |                     services.AddSingleton<MainWindow>(); | ||||||
|  |                     services.AddSingleton<MainWindowViewModel>(); | ||||||
|  | 
 | ||||||
|  |                     services.AddTransient<AudioPlayerViewModel>(); | ||||||
|  |                      | ||||||
|  |                     services.AddSingleton<AudioContext>(); | ||||||
|  |                     services.AddScoped<IAudioPlayer, AudioPlayer>(); | ||||||
|  |                     services.AddSingleton<IAudioConverter, MP3AudioConverter>(); | ||||||
|  |                     services.AddSingleton<IMP3Parser, MP3Parser>(); | ||||||
|  |                     services.AddSingleton<IPackExtractor, CorePackExtractor>(); | ||||||
|  |                     services.AddSingleton<IFileStorage, DiskFileStorage>(); | ||||||
|  |                     services.AddSingleton<IPlatformStorageService, PlatformStorageService>(); | ||||||
|  |                     services.AddSingleton<IFileLoaderService, FileLoaderService>(); | ||||||
|  |                     services.AddSingleton<IConvertService, FileConvertService>(); | ||||||
|  |                 }) | ||||||
|  |                 .Build(); | ||||||
|  |             hostBuilder.Start(); | ||||||
|  |             _container = hostBuilder.Services; | ||||||
|  |             return _container; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										75
									
								
								src/Plpext/Plpext.UI/Plpext.UI.csproj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/Plpext/Plpext.UI/Plpext.UI.csproj
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,75 @@ | ||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <OutputType>WinExe</OutputType> | ||||||
|  |     <TargetFramework>net8.0</TargetFramework> | ||||||
|  |     <Nullable>enable</Nullable> | ||||||
|  |     <BuiltInComInteropSupport>true</BuiltInComInteropSupport> | ||||||
|  |     <ApplicationManifest>app.manifest</ApplicationManifest> | ||||||
|  |     <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> | ||||||
|  |     <Platforms>x86;x64</Platforms> | ||||||
|  |     <AssemblyName>Plpext</AssemblyName> | ||||||
|  |     <SelfContained>true</SelfContained> | ||||||
|  |     <PublishReadyToRun>true</PublishReadyToRun> | ||||||
|  |   </PropertyGroup> | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <Folder Include="Models\" /> | ||||||
|  |     <AvaloniaResource Include="Assets\**" /> | ||||||
|  |     <AvaloniaResource Remove="ViewModels\Mocks\**" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <PackageReference Include="Avalonia" Version="11.2.2" /> | ||||||
|  |     <PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.2" /> | ||||||
|  |     <PackageReference Include="Avalonia.Desktop" Version="11.2.2" /> | ||||||
|  |     <PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.2" /> | ||||||
|  |     <PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.2" /> | ||||||
|  |     <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> | ||||||
|  |     <PackageReference Include="Avalonia.Diagnostics" Version="11.2.2"> | ||||||
|  |       <IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets> | ||||||
|  |       <PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets> | ||||||
|  |     </PackageReference> | ||||||
|  |     <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0-preview3" /> | ||||||
|  |     <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" /> | ||||||
|  |     <PackageReference Include="Serilog" Version="4.1.0" /> | ||||||
|  |     <PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" /> | ||||||
|  |     <PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" /> | ||||||
|  |     <PackageReference Include="Velopack" Version="0.0.942" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\Plpext.Core\Plpext.Core.csproj" /> | ||||||
|  |     <ProjectReference Include="..\..\Plpext.Core.Windows\Plpext.Core.Windows.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <Compile Remove="ViewModels\Mocks\MockAudioPlayerViewModel.cs" /> | ||||||
|  |     <Compile Remove="ViewModels\Mocks\**" /> | ||||||
|  |     <Compile Remove="ViewLocator.cs" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <AvaloniaXaml Remove="ViewModels\Mocks\**" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <EmbeddedResource Remove="ViewModels\Mocks\**" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <AdditionalFiles Remove="ViewModels\Mocks\**" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <None Remove="ViewModels\Mocks\**" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  | <Target Name="CopyOpenALToPublish" AfterTargets="Publish"> | ||||||
|  |   <Copy SourceFiles="$(MSBuildThisFileDirectory)..\..\Plpext.Core.Windows\bin\x64\Release\net8.0\OpenAL32.dll"  | ||||||
|  |         DestinationFolder="$(PublishDir)" /> | ||||||
|  |   <Message Text="OpenAL32.dll copied to publish directory: $(PublishDir)" Importance="high" /> | ||||||
|  | </Target> | ||||||
|  | 
 | ||||||
|  | </Project> | ||||||
							
								
								
									
										26
									
								
								src/Plpext/Plpext.UI/Program.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Plpext/Plpext.UI/Program.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | using System; | ||||||
|  | using Avalonia; | ||||||
|  | using Velopack; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI | ||||||
|  | { | ||||||
|  |     internal sealed class Program | ||||||
|  |     { | ||||||
|  |         // Initialization code. Don't use any Avalonia, third-party APIs or any | ||||||
|  |         // SynchronizationContext-reliant code before AppMain is called: things aren't initialized | ||||||
|  |         // yet and stuff might break. | ||||||
|  |         [STAThread] | ||||||
|  |         public static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             VelopackApp.Build().Run(); | ||||||
|  |             BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Avalonia configuration, don't remove; also used by visual designer. | ||||||
|  |         public static AppBuilder BuildAvaloniaApp() | ||||||
|  |             => AppBuilder.Configure<App>() | ||||||
|  |                 .UsePlatformDetect() | ||||||
|  |                 .WithInterFont() | ||||||
|  |                 .LogToTrace(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/Plpext/Plpext.UI/Resources/Colors.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Plpext/Plpext.UI/Resources/Colors.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | <ResourceDictionary xmlns="https://github.com/avaloniaui" | ||||||
|  |                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> | ||||||
|  |     <Color x:Key="PrimaryBackground">#020615</Color> | ||||||
|  |     <Color x:Key="PrimaryMiddle">#2D3C81</Color> | ||||||
|  |     <Color x:Key="PrimaryForeground">#99A6E5</Color> | ||||||
|  |     <Color x:Key="SecondaryDarkest">#1F0400</Color> | ||||||
|  |     <Color x:Key="SecondaryDark">#661409</Color> | ||||||
|  |     <Color x:Key="SecondaryMiddle">#BD4131</Color> | ||||||
|  |     <Color x:Key="SecondaryLightest">#FFAA9F</Color> | ||||||
|  | 
 | ||||||
|  |     <SolidColorBrush Color="{StaticResource PrimaryBackground}" x:Key="PrimaryBackgroundBrush"></SolidColorBrush> | ||||||
|  |     <SolidColorBrush Color="{StaticResource PrimaryMiddle}" x:Key="PrimaryMiddleBrush"></SolidColorBrush> | ||||||
|  |     <SolidColorBrush Color="{StaticResource PrimaryForeground}" x:Key="PrimaryForegroundBrush"></SolidColorBrush> | ||||||
|  |     <SolidColorBrush Color="{StaticResource SecondaryDarkest}" x:Key="SecondaryDarkestBrush"></SolidColorBrush> | ||||||
|  |     <SolidColorBrush Color="{StaticResource SecondaryDark}" x:Key="SecondaryDarkBrush"></SolidColorBrush> | ||||||
|  |     <SolidColorBrush Color="{StaticResource SecondaryMiddle}" x:Key="SecondaryMiddleBrush"></SolidColorBrush> | ||||||
|  |     <SolidColorBrush Color="{StaticResource SecondaryLightest}" x:Key="SecondaryLightestBrush"></SolidColorBrush> | ||||||
|  | </ResourceDictionary> | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Plpext.Core.Interfaces; | ||||||
|  | using Plpext.Core.Models; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.Services.Converter; | ||||||
|  | 
 | ||||||
|  | public class FileConvertService : IConvertService | ||||||
|  | { | ||||||
|  |     private readonly IMP3Parser _mp3Parser; | ||||||
|  |     private readonly IFileStorage _fileStorage; | ||||||
|  | 
 | ||||||
|  |     public FileConvertService(IMP3Parser mp3Parser, IFileStorage fileStorage) | ||||||
|  |     { | ||||||
|  |         _mp3Parser = mp3Parser; | ||||||
|  |         _fileStorage = fileStorage; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async Task ConvertFilesAsync(IEnumerable<AudioFile> files, string outputFolder) | ||||||
|  |     { | ||||||
|  |         var mp3Files = new List<MP3File>(); | ||||||
|  |         foreach (var file in files) | ||||||
|  |         { | ||||||
|  |             mp3Files.Add(await _mp3Parser.ParseIntoMP3(file.MP3Data, default)); | ||||||
|  |         } | ||||||
|  |         await _fileStorage.SaveFilesAsync(mp3Files, outputFolder); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Plpext.Core.AudioPlayer; | ||||||
|  | using Plpext.Core.Interfaces; | ||||||
|  | using Plpext.Core.Models; | ||||||
|  | using Plpext.UI.ViewModels; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.Services.FileLoader; | ||||||
|  | 
 | ||||||
|  | public class FileLoaderService : IFileLoaderService | ||||||
|  | { | ||||||
|  |     private readonly IPackExtractor _packExtractor; | ||||||
|  |     private readonly IAudioConverter _audioConverter; | ||||||
|  |     private IEnumerable<ReadOnlyMemory<byte>> _currentFile = null!; | ||||||
|  |      | ||||||
|  |     public FileLoaderService(IPackExtractor packExtractor, IAudioConverter audioConverter) | ||||||
|  |     { | ||||||
|  |         _packExtractor = packExtractor; | ||||||
|  |         _audioConverter = audioConverter; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async Task<int> GetFileCountAsync(string filePath) | ||||||
|  |     { | ||||||
|  |         _currentFile = await _packExtractor.GetFileListAsync(filePath, default); | ||||||
|  |         return _currentFile?.Count() ?? 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async IAsyncEnumerable<AudioPlayerViewModel> LoadFilesAsync() | ||||||
|  |     { | ||||||
|  |         foreach (var file in _currentFile) | ||||||
|  |         { | ||||||
|  |             var audioFile = await _audioConverter.ConvertAudioAsync(file, default); | ||||||
|  |             yield return new AudioPlayerViewModel(new AudioPlayer(), audioFile); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								src/Plpext/Plpext.UI/Services/IConvertService.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Plpext/Plpext.UI/Services/IConvertService.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Plpext.Core.Models; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.Services; | ||||||
|  | 
 | ||||||
|  | public interface IConvertService | ||||||
|  | { | ||||||
|  |     public Task ConvertFilesAsync(IEnumerable<AudioFile> files, string outputFolder); | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								src/Plpext/Plpext.UI/Services/IFileLoaderService.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Plpext/Plpext.UI/Services/IFileLoaderService.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Plpext.UI.ViewModels; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.Services; | ||||||
|  | 
 | ||||||
|  | public interface IFileLoaderService | ||||||
|  | { | ||||||
|  |     Task<int> GetFileCountAsync(string filePath); | ||||||
|  |     IAsyncEnumerable<AudioPlayerViewModel> LoadFilesAsync(); | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								src/Plpext/Plpext.UI/Services/IPlatformStorageService.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Plpext/Plpext.UI/Services/IPlatformStorageService.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.Services; | ||||||
|  | 
 | ||||||
|  | public interface IPlatformStorageService | ||||||
|  | { | ||||||
|  |     Task<string> GetOriginFilePath(); | ||||||
|  |     Task<string> GetTargetFolderPath(); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,48 @@ | ||||||
|  | using Avalonia; | ||||||
|  | using Avalonia.Controls.ApplicationLifetimes; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Avalonia.Platform.Storage; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.Services.PlatformStorage | ||||||
|  | { | ||||||
|  |     public class PlatformStorageService : IPlatformStorageService | ||||||
|  |     { | ||||||
|  |         public async Task<string> GetOriginFilePath() | ||||||
|  |         { | ||||||
|  |             if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || | ||||||
|  |                 desktop.MainWindow?.StorageProvider is not { } provider) | ||||||
|  |                 return string.Empty; | ||||||
|  | 
 | ||||||
|  |             var filePath = await provider.OpenFilePickerAsync(new FilePickerOpenOptions() | ||||||
|  |             { | ||||||
|  |                 AllowMultiple = false, | ||||||
|  |                 Title = "Select Plus Library Pack", | ||||||
|  |                 FileTypeFilter = [new ("Plus Library Pack"){Patterns = ["*.plp"]}], | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return filePath.Any() ? filePath[0].Path.AbsolutePath : string.Empty; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         public async Task<string> GetTargetFolderPath() | ||||||
|  |         { | ||||||
|  |             if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || | ||||||
|  |                 desktop.MainWindow?.StorageProvider is not { } provider) | ||||||
|  |                 return string.Empty; | ||||||
|  | 
 | ||||||
|  |             var tentativeFolder = await provider.TryGetWellKnownFolderAsync(Avalonia.Platform.Storage.WellKnownFolder.Music); | ||||||
|  | 
 | ||||||
|  |             var outputPath = await provider.OpenFolderPickerAsync(new Avalonia.Platform.Storage.FolderPickerOpenOptions() | ||||||
|  |             { | ||||||
|  |                 AllowMultiple = false, | ||||||
|  |                 Title = "Select output folder", | ||||||
|  |                 SuggestedStartLocation = tentativeFolder | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return outputPath.Any() ? outputPath[0].Path.AbsolutePath : string.Empty; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								src/Plpext/Plpext.UI/Styles/AudioPlayerControl.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/Plpext/Plpext.UI/Styles/AudioPlayerControl.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | ||||||
|  | <Styles xmlns="https://github.com/avaloniaui" | ||||||
|  |         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||||||
|  |         xmlns:c="clr-namespace:Plpext.UI" | ||||||
|  |         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||||||
|  |         xmlns:vm="clr-namespace:Plpext.UI.ViewModels"> | ||||||
|  |     <Design.PreviewWith> | ||||||
|  |         <Border Padding="20" Background="RoyalBlue" BorderBrush="Black" BorderThickness="1"> | ||||||
|  |             <StackPanel> | ||||||
|  |                 <Border Background="Navy" BorderBrush="White" BorderThickness="1"> | ||||||
|  |                 <c:AudioPlayerControl | ||||||
|  |                     x:DataType="vm:AudioPlayerViewModel" | ||||||
|  |                     IsPlaying="{Binding IsPlaying}" | ||||||
|  |                     PlayCommand="{Binding PlayCommand}" | ||||||
|  |                     StopCommand="{Binding StopCommand}" | ||||||
|  |                     PlaybackState="{Binding PlaybackState}" | ||||||
|  |                 TotalDuration="0:22" | ||||||
|  |                 CurrentDuration="0:15" | ||||||
|  |                 Progress="44.8" | ||||||
|  |                 > | ||||||
|  |                     <c:AudioPlayerControl.DataContext> | ||||||
|  |                         <vm:AudioPlayerViewModel/> | ||||||
|  |                     </c:AudioPlayerControl.DataContext> | ||||||
|  |                 </c:AudioPlayerControl> | ||||||
|  |                     </Border> | ||||||
|  |                 <c:AudioPlayerControl | ||||||
|  |                     x:DataType="vm:AudioPlayerViewModel" | ||||||
|  |                     IsPlaying="True" | ||||||
|  |                     PlayCommand="{Binding PlayCommand}" | ||||||
|  |                     TotalDuration="22" | ||||||
|  |                     CurrentDuration="15" | ||||||
|  |                     Progress="44.8" | ||||||
|  |                 > | ||||||
|  |                     <c:AudioPlayerControl.DataContext> | ||||||
|  |                         <vm:AudioPlayerViewModel/> | ||||||
|  |                     </c:AudioPlayerControl.DataContext> | ||||||
|  |                 </c:AudioPlayerControl> | ||||||
|  |             </StackPanel> | ||||||
|  |         </Border> | ||||||
|  |     </Design.PreviewWith> | ||||||
|  |      | ||||||
|  |     <Style Selector="c|AudioPlayerControl"> | ||||||
|  |         <Setter Property="FontSize" Value="14"/> | ||||||
|  |         <Setter Property="Margin" Value="0,0,0,0"/> | ||||||
|  |         <Setter Property="Width" Value="176"></Setter> | ||||||
|  |         <Setter Property="Height" Value="36"></Setter> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="c|AudioPlayerControl Button"> | ||||||
|  |         <Setter Property="Foreground" Value="{StaticResource SecondaryLightest}"/> | ||||||
|  |         <Setter Property="Background" Value="{StaticResource SecondaryDarkest}"/> | ||||||
|  |         <Setter Property="BorderThickness" Value="2"/> | ||||||
|  |         <Setter Property="BorderBrush" Value="{StaticResource SecondaryDark}"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="c|AudioPlayerControl Button:pointerover"> | ||||||
|  |         <Setter Property="Foreground" Value="{StaticResource SecondaryMiddle}"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="c|AudioPlayerControl Button:pointerover /template/ ContentPresenter"> | ||||||
|  |         <Setter Property="Background" Value="{StaticResource SecondaryDark}"/> | ||||||
|  |         <Setter Property="BorderThickness" Value="2"/> | ||||||
|  |         <Setter Property="BorderBrush" Value="{StaticResource SecondaryDarkest}"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="c|AudioPlayerControl[IsPlaying=False] /template/ Grid#ButtonGrid"> | ||||||
|  |         <Setter Property="Margin" Value="142,0,-32,0"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="c|AudioPlayerControl[IsPlaying=True] /template/ Grid#ButtonGrid"> | ||||||
|  |         <Setter Property="Margin" Value="0,0,0,0"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="c|AudioPlayerControl[IsPlaying=False] /template/ StackPanel#ProgressPanel"> | ||||||
|  |         <Setter Property="Width" Value="0"/> | ||||||
|  |         <Setter Property="Opacity" Value="0"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="c|AudioPlayerControl[IsPlaying=True] /template/ StackPanel#ProgressPanel"> | ||||||
|  |         <Setter Property="UseLayoutRounding" Value="True"/> | ||||||
|  |         <Setter Property="Opacity" Value="1"/> | ||||||
|  |         <Setter Property="Margin" Value="6,10,0,0"/> | ||||||
|  |         <Setter Property="VerticalAlignment" Value="Center"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="c|AudioPlayerControl[IsPlaying=False] /template/ ProgressBar:horizontal"> | ||||||
|  |         <Setter Property="MinWidth" Value="0"/> | ||||||
|  |         <Setter Property="Width" Value="0"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="c|AudioPlayerControl[IsPlaying=True] /template/ ProgressBar:horizontal"> | ||||||
|  |         <Setter Property="MinWidth" Value="0"/> | ||||||
|  |         <Setter Property="Width" Value="100"/> | ||||||
|  |     </Style> | ||||||
|  | 
 | ||||||
|  | </Styles> | ||||||
							
								
								
									
										15
									
								
								src/Plpext/Plpext.UI/Styles/Border.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Plpext/Plpext.UI/Styles/Border.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | <Styles xmlns="https://github.com/avaloniaui" | ||||||
|  |         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> | ||||||
|  |     <Design.PreviewWith> | ||||||
|  |         <Border Padding="20"> | ||||||
|  |             <!-- Add Controls for Previewer Here --> | ||||||
|  |         </Border> | ||||||
|  |     </Design.PreviewWith> | ||||||
|  |      | ||||||
|  |     <Style Selector="Border.Section"> | ||||||
|  |         <Setter Property="BorderBrush" Value="{StaticResource PrimaryForeground}"/> | ||||||
|  |         <Setter Property="BorderThickness" Value="1"/> | ||||||
|  |         <Setter Property="BoxShadow" Value="0 0 1 1 Black"/> | ||||||
|  |         <Setter Property="CornerRadius" Value="1"/> | ||||||
|  |     </Style> | ||||||
|  | </Styles> | ||||||
							
								
								
									
										30
									
								
								src/Plpext/Plpext.UI/Styles/Button.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Plpext/Plpext.UI/Styles/Button.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | ||||||
|  | <Styles xmlns="https://github.com/avaloniaui" | ||||||
|  |         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> | ||||||
|  |     <Design.PreviewWith> | ||||||
|  |         <Border Padding="20"> | ||||||
|  |             <StackPanel> | ||||||
|  |                 <Button Classes="Primary" Content="Primary Button" /> | ||||||
|  |                 <Button IsEnabled="False" Classes="Primary" Content="Primary Button" /> | ||||||
|  |             </StackPanel>            <!-- Add Controls for Previewer Here --> | ||||||
|  |         </Border> | ||||||
|  |     </Design.PreviewWith> | ||||||
|  |      | ||||||
|  |     <Style Selector="Button.Primary"> | ||||||
|  |         <Setter Property="BorderThickness" Value="1"/> | ||||||
|  |         <Setter Property="BorderBrush" Value="{StaticResource SecondaryDarkestBrush}"/> | ||||||
|  |         <Setter Property="Padding" Value="8 12 8 12"/> | ||||||
|  |         <Setter Property="CornerRadius" Value="4"/> | ||||||
|  |         <Setter Property="Margin" Value="2"/> | ||||||
|  |         <Setter Property="Background" Value="{StaticResource SecondaryDark}"/> | ||||||
|  |         <Setter Property="Foreground" Value="{StaticResource SecondaryLightest}"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="Button.Primary:disabled /template/ ContentPresenter"> | ||||||
|  |         <Setter Property="Background" Value="{StaticResource SecondaryDarkest}"/> | ||||||
|  |         <Setter Property="Foreground" Value="Gray"/> | ||||||
|  |     </Style> | ||||||
|  |     <Style Selector="Button.Primary:pointerover /template/ ContentPresenter"> | ||||||
|  |         <Setter Property="Background" Value="{StaticResource SecondaryLightest}"/> | ||||||
|  |         <Setter Property="Foreground" Value="{StaticResource SecondaryDarkest}"/> | ||||||
|  |     </Style> | ||||||
|  | 
 | ||||||
|  | </Styles> | ||||||
							
								
								
									
										14
									
								
								src/Plpext/Plpext.UI/Styles/ProgressBar.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Plpext/Plpext.UI/Styles/ProgressBar.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | <Styles xmlns="https://github.com/avaloniaui" | ||||||
|  |         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> | ||||||
|  |     <Design.PreviewWith> | ||||||
|  |         <Border Padding="20"> | ||||||
|  |             <ProgressBar IsIndeterminate="True"></ProgressBar> | ||||||
|  |         </Border> | ||||||
|  |     </Design.PreviewWith> | ||||||
|  | 
 | ||||||
|  |     <Style Selector="ProgressBar"> | ||||||
|  |         <Setter Property="Background" Value="{StaticResource SecondaryDark}"/> | ||||||
|  |         <Setter Property="Foreground" Value="{StaticResource SecondaryLightest}"/> | ||||||
|  |         <Setter Property="CornerRadius" Value="0"/> | ||||||
|  |     </Style> | ||||||
|  | </Styles> | ||||||
							
								
								
									
										15
									
								
								src/Plpext/Plpext.UI/Styles/TextBox.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Plpext/Plpext.UI/Styles/TextBox.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | <Styles xmlns="https://github.com/avaloniaui" | ||||||
|  |         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> | ||||||
|  |     <Design.PreviewWith> | ||||||
|  |         <Border Padding="20"> | ||||||
|  |             <!-- Add Controls for Previewer Here --> | ||||||
|  |             <TextBox Classes="Primary"></TextBox> | ||||||
|  |         </Border> | ||||||
|  |     </Design.PreviewWith> | ||||||
|  |      | ||||||
|  |     <Style Selector="TextBox.Primary"> | ||||||
|  |         <Setter Property="Height" Value="32"/> | ||||||
|  |          | ||||||
|  |     </Style> | ||||||
|  | 
 | ||||||
|  | </Styles> | ||||||
							
								
								
									
										9
									
								
								src/Plpext/Plpext.UI/Styles/Window.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Plpext/Plpext.UI/Styles/Window.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | <Styles xmlns="https://github.com/avaloniaui" | ||||||
|  |         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> | ||||||
|  |     <Design.PreviewWith> | ||||||
|  |     </Design.PreviewWith> | ||||||
|  |      | ||||||
|  |     <Style Selector="Window"> | ||||||
|  |         <Setter Property="Background" Value="{StaticResource PrimaryBackground}"></Setter> | ||||||
|  |     </Style> | ||||||
|  | </Styles> | ||||||
							
								
								
									
										120
									
								
								src/Plpext/Plpext.UI/ViewModels/AudioPlayerViewModel.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/Plpext/Plpext.UI/ViewModels/AudioPlayerViewModel.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,120 @@ | ||||||
|  | using CommunityToolkit.Mvvm.ComponentModel; | ||||||
|  | using Plpext.Core.AudioPlayer; | ||||||
|  | using Plpext.Core.Models; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.ComponentModel; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using CommunityToolkit.Mvvm.Input; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.ViewModels | ||||||
|  | { | ||||||
|  |     public partial class AudioPlayerViewModel : ViewModelBase, IDisposable | ||||||
|  |     { | ||||||
|  |         private readonly AudioPlayer _audioPlayer = null!; | ||||||
|  |         private readonly AudioFile _audioFile = null!; | ||||||
|  |         private bool _firstExecution = true; | ||||||
|  |          | ||||||
|  |         public AudioFile AudioFile | ||||||
|  |         { | ||||||
|  |             get { return _audioFile; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public AudioPlayerViewModel() | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public AudioPlayerViewModel(AudioPlayer audioPlayer, AudioFile audioFile) | ||||||
|  |         { | ||||||
|  |             _audioPlayer = audioPlayer; | ||||||
|  |             _audioFile = audioFile; | ||||||
|  |             CurrentDuration = "0:00"; | ||||||
|  |             Name = _audioFile.Name; | ||||||
|  |             TotalDuration = $"{_audioFile.Duration:m\\:ss}"; | ||||||
|  |             audioPlayer.OnProgressUpdated += OnProgressUpdated; | ||||||
|  |             audioPlayer.OnPlaybackStopped += OnPlaybackStopped; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private void OnPlaybackStopped(object? sender, PlaybackStoppedEventArgs e) | ||||||
|  |         { | ||||||
|  |             IsPlaying = false; | ||||||
|  |             PlaybackState = PlaybackState.Stopped; | ||||||
|  |             CurrentDuration = "0:00"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private void OnProgressUpdated(object? sender, PlaybackProgress e) | ||||||
|  |         { | ||||||
|  |             Progress = e.ProgressPercentage; | ||||||
|  |             CurrentDuration = $"{e.CurrentPosition:m\\:ss}"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [ObservableProperty] | ||||||
|  |         private bool _isSelected; | ||||||
|  | 
 | ||||||
|  |         [ObservableProperty] | ||||||
|  |         private bool _isPlaying; | ||||||
|  | 
 | ||||||
|  |         [ObservableProperty] | ||||||
|  |         private string _totalDuration = null!; | ||||||
|  | 
 | ||||||
|  |         [ObservableProperty] | ||||||
|  |         private string _currentDuration = null!; | ||||||
|  | 
 | ||||||
|  |         [ObservableProperty] | ||||||
|  |         private string _name = null!; | ||||||
|  | 
 | ||||||
|  |         [ObservableProperty] private PlaybackState _playbackState = PlaybackState.Stopped; | ||||||
|  |          | ||||||
|  |         [ObservableProperty] | ||||||
|  |         private double _progress; | ||||||
|  | 
 | ||||||
|  |         [RelayCommand] | ||||||
|  |         private async Task Play() | ||||||
|  |         { | ||||||
|  |             if (IsPlaying) | ||||||
|  |             { | ||||||
|  |                 if (PlaybackState == PlaybackState.Playing) | ||||||
|  |                 { | ||||||
|  |                     _audioPlayer.Pause(); | ||||||
|  |                     PlaybackState = PlaybackState.Paused; | ||||||
|  |                 } | ||||||
|  |                 else if (PlaybackState == PlaybackState.Paused) | ||||||
|  |                 { | ||||||
|  |                     _audioPlayer.Resume(); | ||||||
|  |                     PlaybackState = PlaybackState.Playing; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             IsPlaying = true; | ||||||
|  |             if (_firstExecution) | ||||||
|  |             { | ||||||
|  |                 _firstExecution = false; | ||||||
|  |                 await _audioPlayer.InitAudioPlayerAsync(_audioFile, true, default); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |                 await _audioPlayer.Start(); | ||||||
|  |             PlaybackState = PlaybackState.Playing; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [RelayCommand] | ||||||
|  |         private Task Stop() | ||||||
|  |         { | ||||||
|  |             IsPlaying = false; | ||||||
|  |             _audioPlayer.Stop(); | ||||||
|  |             PlaybackState = PlaybackState.Stopped; | ||||||
|  |             return Task.CompletedTask; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         public void Dispose() | ||||||
|  |         { | ||||||
|  |             _audioPlayer.OnPlaybackStopped -= OnPlaybackStopped; | ||||||
|  |             _audioPlayer.OnProgressUpdated -= OnProgressUpdated; | ||||||
|  |             _audioPlayer.Dispose(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										129
									
								
								src/Plpext/Plpext.UI/ViewModels/MainWindowViewModel.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/Plpext/Plpext.UI/ViewModels/MainWindowViewModel.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,129 @@ | ||||||
|  | using CommunityToolkit.Mvvm.ComponentModel; | ||||||
|  | using CommunityToolkit.Mvvm.Input; | ||||||
|  | using Plpext.UI.Services; | ||||||
|  | using System.Collections.ObjectModel; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Avalonia.Threading; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.ViewModels; | ||||||
|  | 
 | ||||||
|  | public partial class MainWindowViewModel : ViewModelBase | ||||||
|  | { | ||||||
|  |     private readonly IPlatformStorageService _platformStorageService = null!; | ||||||
|  |     private readonly IFileLoaderService _fileLoaderService = null!; | ||||||
|  |     private readonly IConvertService _convertService = null!; | ||||||
|  | 
 | ||||||
|  |     public MainWindowViewModel() | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public MainWindowViewModel(IPlatformStorageService platformStorageService, IFileLoaderService fileLoaderService, | ||||||
|  |         IConvertService convertService) | ||||||
|  |     { | ||||||
|  |         _fileLoaderService = fileLoaderService; | ||||||
|  |         _platformStorageService = platformStorageService; | ||||||
|  |         _convertService = convertService; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(LoadFileCommand))] | ||||||
|  |     private string _originPath = null!; | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ConvertAllFilesCommand),nameof(ConvertSelectedFilesCommand))] | ||||||
|  |     private string _targetPath = null!; | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] private int _totalFilesToExtract; | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] private int _filesReady; | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] private string _progressBarText = null!; | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] private double _progressBarValue; | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] private bool _isProgressBarIndeterminate; | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] private string _progressBarDetails = null!; | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ConvertAllFilesCommand), nameof(ConvertSelectedFilesCommand), nameof(LoadFileCommand))] | ||||||
|  |     private bool _showProgressBar; | ||||||
|  | 
 | ||||||
|  |     [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ConvertAllFilesCommand),nameof(ConvertSelectedFilesCommand))] | ||||||
|  |     private ObservableCollection<AudioPlayerViewModel> _audioFiles = new(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     [RelayCommand(CanExecute = nameof(CanLoadFile))] | ||||||
|  |     private async Task LoadFile() | ||||||
|  |     { | ||||||
|  |         TotalFilesToExtract = await _fileLoaderService.GetFileCountAsync(OriginPath); | ||||||
|  | 
 | ||||||
|  |         await Dispatcher.UIThread.InvokeAsync(() => | ||||||
|  |         { | ||||||
|  |             ProgressBarText = "Loading files..."; | ||||||
|  |             ProgressBarValue = 0 / (double)TotalFilesToExtract * 100; | ||||||
|  |             ShowProgressBar = true; | ||||||
|  |             IsProgressBarIndeterminate = false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         await foreach (var item in _fileLoaderService.LoadFilesAsync()) | ||||||
|  |         { | ||||||
|  |             await Dispatcher.UIThread.InvokeAsync(() => | ||||||
|  |             { | ||||||
|  |                 AudioFiles.Add(item); | ||||||
|  |                 FilesReady += 1; | ||||||
|  |                 ProgressBarValue = FilesReady / (double)TotalFilesToExtract * 100; | ||||||
|  |                 ProgressBarDetails = $"{FilesReady} of {TotalFilesToExtract} ({ProgressBarValue:00.0}%)"; | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         await Dispatcher.UIThread.InvokeAsync(() => ShowProgressBar = false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private bool CanLoadFile() => !string.IsNullOrEmpty(OriginPath) && !ShowProgressBar; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     [RelayCommand] | ||||||
|  |     private async Task SelectOriginPath() | ||||||
|  |     { | ||||||
|  |         OriginPath = await _platformStorageService.GetOriginFilePath(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [RelayCommand] | ||||||
|  |     private async Task SelectTargetPath() | ||||||
|  |     { | ||||||
|  |         TargetPath = await _platformStorageService.GetTargetFolderPath(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [RelayCommand(CanExecute = nameof(CanConvertAllFiles))] | ||||||
|  |     private async Task ConvertAllFiles() | ||||||
|  |     { | ||||||
|  |         await Dispatcher.UIThread.InvokeAsync(() => | ||||||
|  |         { | ||||||
|  |             ProgressBarText = $"Converting {TotalFilesToExtract} files..."; | ||||||
|  |             IsProgressBarIndeterminate = true; | ||||||
|  |             ShowProgressBar = true; | ||||||
|  |             ProgressBarDetails = string.Empty; | ||||||
|  |         }); | ||||||
|  |         await _convertService.ConvertFilesAsync(AudioFiles.Select(x => x.AudioFile), TargetPath); | ||||||
|  |         await Dispatcher.UIThread.InvokeAsync(() => ShowProgressBar = false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private bool CanConvertAllFiles() => !string.IsNullOrEmpty(TargetPath) && AudioFiles.Any() && !ShowProgressBar; | ||||||
|  |     private bool CanConvertSelectFiles() => CanConvertAllFiles(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     [RelayCommand(CanExecute = nameof(CanConvertSelectFiles))] | ||||||
|  |     private async Task ConvertSelectedFiles() | ||||||
|  |     { | ||||||
|  |         var filesToConvert = AudioFiles.Where(a => a.IsSelected).Select(x => x.AudioFile).ToArray(); | ||||||
|  |         if (filesToConvert.Length == 0) return; | ||||||
|  |         await Dispatcher.UIThread.InvokeAsync(() => | ||||||
|  |         { | ||||||
|  |             ProgressBarText = $"Converting {filesToConvert.Length} files..."; | ||||||
|  |             IsProgressBarIndeterminate = true; | ||||||
|  |             ShowProgressBar = true; | ||||||
|  |             ProgressBarDetails = string.Empty; | ||||||
|  |         }); | ||||||
|  |         await _convertService.ConvertFilesAsync(filesToConvert, TargetPath); | ||||||
|  |         await Dispatcher.UIThread.InvokeAsync(() => ShowProgressBar = false); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,105 @@ | ||||||
|  | using System; | ||||||
|  | using System.ComponentModel; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using System.Windows.Input; | ||||||
|  | using Plpext.Core.AudioPlayer; | ||||||
|  | using Plpext.Core.Models; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.ViewModels.Mocks; | ||||||
|  | 
 | ||||||
|  |     public static class MockAudioPlayerViewModel | ||||||
|  |     { | ||||||
|  |         private static readonly AudioPlayer _audioPlayer = new AudioPlayer(); | ||||||
|  |         private static readonly AudioFile _audioFile = new AudioFile() | ||||||
|  |         { Name = "Dummy", Duration = TimeSpan.FromSeconds(14), Format = AudioFormat.Mono16, Frequency = 44100 }; | ||||||
|  | 
 | ||||||
|  |         static MockAudioPlayerViewModel() | ||||||
|  |         { | ||||||
|  |             Instance = new MockAudioPlayerViewModelInstance(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public static MockAudioPlayerViewModelInstance Instance { get; } | ||||||
|  | 
 | ||||||
|  |         public class MockAudioPlayerViewModelInstance : INotifyPropertyChanged | ||||||
|  |         { | ||||||
|  |             public MockAudioPlayerViewModelInstance() | ||||||
|  |             { | ||||||
|  |                 PlayCommand = new RelayCommand(async () => await Play()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             private bool _isPlaying; | ||||||
|  |             public bool IsPlaying | ||||||
|  |             { | ||||||
|  |                 get => _isPlaying; | ||||||
|  |                 set => SetProperty(ref _isPlaying, value); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             private string _totalDuration; | ||||||
|  |             public string TotalDuration | ||||||
|  |             { | ||||||
|  |                 get => _totalDuration; | ||||||
|  |                 set => SetProperty(ref _totalDuration, value); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             private string _currentDuration; | ||||||
|  |             public string CurrentDuration | ||||||
|  |             { | ||||||
|  |                 get => _currentDuration; | ||||||
|  |                 set => SetProperty(ref _currentDuration, value); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             private string _name; | ||||||
|  |             public string Name | ||||||
|  |             { | ||||||
|  |                 get => _name; | ||||||
|  |                 set => SetProperty(ref _name, value); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             public ICommand PlayCommand { get; } | ||||||
|  | 
 | ||||||
|  |             private async Task Play() | ||||||
|  |             { | ||||||
|  |                 IsPlaying = !IsPlaying; | ||||||
|  |                 // Implement play logic here | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             public event PropertyChangedEventHandler PropertyChanged; | ||||||
|  | 
 | ||||||
|  |             protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) | ||||||
|  |             { | ||||||
|  |                 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null) | ||||||
|  |             { | ||||||
|  |                 if (Equals(field, value)) return false; | ||||||
|  |                 field = value; | ||||||
|  |                 OnPropertyChanged(propertyName); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public class RelayCommand : ICommand | ||||||
|  |     { | ||||||
|  |         private readonly Func<Task> _execute; | ||||||
|  |         private readonly Func<bool> _canExecute; | ||||||
|  | 
 | ||||||
|  |         public RelayCommand(Func<Task> execute, Func<bool> canExecute = null) | ||||||
|  |         { | ||||||
|  |             _execute = execute ?? throw new ArgumentNullException(nameof(execute)); | ||||||
|  |             _canExecute = canExecute; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public bool CanExecute(object parameter) => _canExecute == null || _canExecute(); | ||||||
|  | 
 | ||||||
|  |         public async void Execute(object parameter) => await _execute(); | ||||||
|  | 
 | ||||||
|  |         public event EventHandler CanExecuteChanged; | ||||||
|  | 
 | ||||||
|  |         public void RaiseCanExecuteChanged() | ||||||
|  |         { | ||||||
|  |             CanExecuteChanged?.Invoke(this, EventArgs.Empty); | ||||||
|  |         } | ||||||
|  |     } | ||||||
							
								
								
									
										187
									
								
								src/Plpext/Plpext.UI/Views/MainWindow.axaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								src/Plpext/Plpext.UI/Views/MainWindow.axaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,187 @@ | ||||||
|  | <Window | ||||||
|  |     x:Class="Plpext.UI.Views.MainWindow" | ||||||
|  |     xmlns="https://github.com/avaloniaui" | ||||||
|  |     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||||||
|  |     xmlns:c="using:Plpext.UI" | ||||||
|  |     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||||||
|  |     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||||||
|  |     xmlns:vm="using:Plpext.UI.ViewModels" | ||||||
|  |     Title="Plpext" | ||||||
|  |     d:DesignHeight="450" | ||||||
|  |     d:DesignWidth="800" | ||||||
|  |     Width="800" | ||||||
|  |     Height="450" | ||||||
|  |     x:DataType="vm:MainWindowViewModel" | ||||||
|  |     Icon="/Assets/plpext.png" | ||||||
|  |     mc:Ignorable="d"> | ||||||
|  |     <Window.Styles> | ||||||
|  |         <StyleInclude Source="/Styles/Button.axaml" /> | ||||||
|  |         <StyleInclude Source="/Styles/AudioPlayerControl.axaml" /> | ||||||
|  |         <StyleInclude Source="/Styles/Window.axaml" /> | ||||||
|  |         <StyleInclude Source="/Styles/Border.axaml" /> | ||||||
|  |         <StyleInclude Source="/Styles/TextBox.axaml" /> | ||||||
|  |         <StyleInclude Source="/Styles/ProgressBar.axaml" /> | ||||||
|  |     </Window.Styles> | ||||||
|  | 
 | ||||||
|  |     <Design.DataContext> | ||||||
|  |         <!-- | ||||||
|  |             This only sets the DataContext for the previewer in an IDE, | ||||||
|  |             to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) | ||||||
|  |         --> | ||||||
|  |         <vm:MainWindowViewModel /> | ||||||
|  |     </Design.DataContext> | ||||||
|  | 
 | ||||||
|  |     <Grid | ||||||
|  |         Margin="16, 8, 16, 8" | ||||||
|  |         ColumnDefinitions="300,32,*" | ||||||
|  |         RowDefinitions="Auto,Auto,*"> | ||||||
|  |         <Border Grid.Row="0" | ||||||
|  |                 Grid.Column="0" Classes="Section"> | ||||||
|  | 
 | ||||||
|  |             <StackPanel Orientation="Vertical"> | ||||||
|  |                 <Grid | ||||||
|  |                     Margin="8" | ||||||
|  |                     ColumnDefinitions="*" | ||||||
|  |                     RowDefinitions="*, Auto, Auto"> | ||||||
|  |                     <Label Grid.Row="0" Content="Select your .plp pack file:" /> | ||||||
|  |                     <StackPanel Height="36" | ||||||
|  |                                 Grid.Row="1" | ||||||
|  |                                 Orientation="Horizontal" | ||||||
|  |                                 Spacing="2"> | ||||||
|  |                         <TextBox | ||||||
|  |                             Width="248" | ||||||
|  |                             HorizontalContentAlignment="Stretch" | ||||||
|  |                             VerticalContentAlignment="Center" | ||||||
|  |                             Classes="Primary" | ||||||
|  |                             Text="{Binding OriginPath}" /> | ||||||
|  |                         <Button Classes="Primary" Command="{Binding SelectOriginPathCommand}"> | ||||||
|  |                             <TextBlock Height="24">...</TextBlock> | ||||||
|  |                         </Button> | ||||||
|  |                     </StackPanel> | ||||||
|  |                     <Button | ||||||
|  |                         Height="38" | ||||||
|  |                         Grid.Row="2" | ||||||
|  |                         HorizontalAlignment="Right" | ||||||
|  |                         Margin="0 4 0 0" | ||||||
|  |                         Classes="Primary" | ||||||
|  |                         Command="{Binding LoadFileCommand}"> | ||||||
|  |                         <TextBlock VerticalAlignment="Center">Load</TextBlock> | ||||||
|  |                     </Button> | ||||||
|  |                 </Grid> | ||||||
|  |                 <Grid | ||||||
|  |                     Margin="8" | ||||||
|  |                     ColumnDefinitions="*, *" | ||||||
|  |                     RowDefinitions="*, Auto, Auto"> | ||||||
|  |                     <Label | ||||||
|  |                         Grid.Row="0" | ||||||
|  |                         Grid.Column="0" | ||||||
|  |                         Grid.ColumnSpan="2" | ||||||
|  |                         Content="Select output folder:" /> | ||||||
|  |                     <StackPanel | ||||||
|  |                         Height="36" | ||||||
|  |                         Grid.Row="1" | ||||||
|  |                         Grid.Column="0" | ||||||
|  |                         Grid.ColumnSpan="2" | ||||||
|  |                         Orientation="Horizontal" | ||||||
|  |                         Spacing="2"> | ||||||
|  |                         <TextBox | ||||||
|  |                             Width="248" | ||||||
|  |                             VerticalContentAlignment="Center" | ||||||
|  |                             Classes="Primary" | ||||||
|  |                             Text="{Binding TargetPath}" /> | ||||||
|  |                         <Button Classes="Primary" Command="{Binding SelectTargetPathCommand}"> | ||||||
|  |                             <TextBlock Height="24">...</TextBlock> | ||||||
|  |                         </Button> | ||||||
|  |                     </StackPanel> | ||||||
|  |                     <Button | ||||||
|  |                         HorizontalAlignment="Left" | ||||||
|  |                         Height="38" | ||||||
|  |                         Grid.Row="2" | ||||||
|  |                         Grid.Column="0" | ||||||
|  |                         Classes="Primary" | ||||||
|  |                         Command="{Binding ConvertAllFilesCommand}"> | ||||||
|  |                         <TextBlock  VerticalAlignment="Center">Extract All</TextBlock> | ||||||
|  |                     </Button> | ||||||
|  |                     <Button | ||||||
|  |                         Height="38" | ||||||
|  |                         HorizontalAlignment="Right" | ||||||
|  |                         Grid.Row="2" | ||||||
|  |                         Grid.Column="1" | ||||||
|  |                         Classes="Primary" | ||||||
|  |                         Command="{Binding ConvertSelectedFilesCommand}"> | ||||||
|  |                         <TextBlock VerticalAlignment="Center">Extract Selected</TextBlock> | ||||||
|  |                     </Button> | ||||||
|  |                 </Grid> | ||||||
|  | 
 | ||||||
|  |             </StackPanel> | ||||||
|  | 
 | ||||||
|  |         </Border> | ||||||
|  |         <Grid Grid.Row="2" Grid.Column="0"> | ||||||
|  |             <StackPanel Orientation="Vertical" VerticalAlignment="Center" Height="60" IsVisible="{Binding ShowProgressBar}"> | ||||||
|  |             <Label Content="{Binding ProgressBarText}"/> | ||||||
|  |                 <Border Classes="Section"> | ||||||
|  |                     <ProgressBar Width="298" Orientation="Horizontal" Height="50" IsIndeterminate="{Binding IsProgressBarIndeterminate}" Value="{Binding ProgressBarValue}" /> | ||||||
|  |                 </Border> | ||||||
|  |             <Label Content="{Binding ProgressBarDetails}" HorizontalAlignment="Right"/> | ||||||
|  |             </StackPanel> | ||||||
|  |         </Grid> | ||||||
|  | 
 | ||||||
|  |         <Grid | ||||||
|  |             Grid.Row="0" | ||||||
|  |             Grid.RowSpan="3" | ||||||
|  |             Grid.Column="2"> | ||||||
|  |             <StackPanel Orientation="Vertical"> | ||||||
|  |                 <StackPanel Orientation="Horizontal"> | ||||||
|  |                     <Label Content="Audio Files" /> | ||||||
|  |                 </StackPanel> | ||||||
|  |                 <DataGrid | ||||||
|  |                     MaxHeight="410" | ||||||
|  |                     CanUserSortColumns="False" | ||||||
|  |                     CanUserResizeColumns="False" | ||||||
|  |                     CanUserReorderColumns="False" | ||||||
|  |                     AreRowDetailsFrozen="True" | ||||||
|  |                     HeadersVisibility="None" | ||||||
|  |                     AutoGenerateColumns="False" | ||||||
|  |                     VerticalScrollBarVisibility="Visible" | ||||||
|  |                     IsReadOnly="False" | ||||||
|  |                     SelectionMode="Extended" | ||||||
|  |                     ItemsSource="{Binding AudioFiles}"> | ||||||
|  |                     <DataGrid.Columns> | ||||||
|  |                         <DataGridTemplateColumn Width="36"> | ||||||
|  |                             <DataGridTemplateColumn.CellTemplate> | ||||||
|  |                                 <DataTemplate> | ||||||
|  |                                     <CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" | ||||||
|  |                                               HorizontalAlignment="Center" | ||||||
|  |                                               VerticalAlignment="Center" /> | ||||||
|  |                                 </DataTemplate> | ||||||
|  |                             </DataGridTemplateColumn.CellTemplate> | ||||||
|  |                         </DataGridTemplateColumn> | ||||||
|  |                         <DataGridTemplateColumn Width="*"> | ||||||
|  |                             <DataGridTemplateColumn.CellTemplate> | ||||||
|  |                                 <DataTemplate DataType="vm:AudioPlayerViewModel"> | ||||||
|  |                                     <Label VerticalAlignment="Center" Content="{Binding Name}" /> | ||||||
|  |                                 </DataTemplate> | ||||||
|  |                             </DataGridTemplateColumn.CellTemplate> | ||||||
|  |                         </DataGridTemplateColumn> | ||||||
|  |                         <DataGridTemplateColumn Width="200" MinWidth="200"> | ||||||
|  |                             <DataGridTemplateColumn.CellTemplate> | ||||||
|  |                                 <DataTemplate DataType="vm:AudioPlayerViewModel"> | ||||||
|  |                                     <c:AudioPlayerControl | ||||||
|  |                                         Margin="0 0 16 0" | ||||||
|  |                                         CurrentDuration="{Binding CurrentDuration}" | ||||||
|  |                                         TotalDuration="{Binding TotalDuration}" | ||||||
|  |                                         PlayCommand="{Binding PlayCommand}" | ||||||
|  |                                         StopCommand="{Binding StopCommand}" | ||||||
|  |                                         IsPlaying="{Binding IsPlaying}" | ||||||
|  |                                         PlaybackState="{Binding PlaybackState}" | ||||||
|  |                                         Progress="{Binding Progress}" /> | ||||||
|  |                                 </DataTemplate> | ||||||
|  |                             </DataGridTemplateColumn.CellTemplate> | ||||||
|  |                         </DataGridTemplateColumn> | ||||||
|  |                     </DataGrid.Columns> | ||||||
|  |                 </DataGrid> | ||||||
|  |             </StackPanel> | ||||||
|  |         </Grid> | ||||||
|  |     </Grid> | ||||||
|  | 
 | ||||||
|  | </Window> | ||||||
							
								
								
									
										12
									
								
								src/Plpext/Plpext.UI/Views/MainWindow.axaml.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Plpext/Plpext.UI/Views/MainWindow.axaml.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | using Avalonia.Controls; | ||||||
|  | 
 | ||||||
|  | namespace Plpext.UI.Views | ||||||
|  | { | ||||||
|  |     public partial class MainWindow : Window | ||||||
|  |     { | ||||||
|  |         public MainWindow() | ||||||
|  |         { | ||||||
|  |             InitializeComponent(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/Plpext/Plpext.UI/app.manifest
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Plpext/Plpext.UI/app.manifest
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> | ||||||
|  |   <!-- This manifest is used on Windows only. | ||||||
|  |        Don't remove it as it might cause problems with window transparency and embedded controls. | ||||||
|  |        For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests --> | ||||||
|  |   <assemblyIdentity version="1.0.0.0" name="Plpext.UI.Desktop"/> | ||||||
|  | 
 | ||||||
|  |   <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> | ||||||
|  |     <application> | ||||||
|  |       <!-- A list of the Windows versions that this application has been tested on | ||||||
|  |            and is designed to work with. Uncomment the appropriate elements | ||||||
|  |            and Windows will automatically select the most compatible environment. --> | ||||||
|  | 
 | ||||||
|  |       <!-- Windows 10 --> | ||||||
|  |       <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> | ||||||
|  |     </application> | ||||||
|  |   </compatibility> | ||||||
|  | </assembly> | ||||||
							
								
								
									
										6
									
								
								src/Plpext/Plpext.UI/velopack.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/Plpext/Plpext.UI/velopack.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | { | ||||||
|  |     "version": "1.0.0", | ||||||
|  |     "executable": "Plpext.exe", | ||||||
|  |     "iconFile": "Assets/plpext.png", | ||||||
|  |     "splashImage": "Assets/plpext.png" | ||||||
|  |   } | ||||||
							
								
								
									
										62
									
								
								src/Plpext/Plpext.sln
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/Plpext/Plpext.sln
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | ||||||
|  |  | ||||||
|  | Microsoft Visual Studio Solution File, Format Version 12.00 | ||||||
|  | # Visual Studio Version 17 | ||||||
|  | VisualStudioVersion = 17.12.35521.163 | ||||||
|  | MinimumVisualStudioVersion = 10.0.40219.1 | ||||||
|  | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plpext.UI", "Plpext.UI\Plpext.UI.csproj", "{CEC79B8A-12B4-4649-B859-08051B00FA96}" | ||||||
|  | EndProject | ||||||
|  | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plpext.Core", "..\Plpext.Core\Plpext.Core.csproj", "{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}" | ||||||
|  | EndProject | ||||||
|  | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plpext.Core.Windows", "..\Plpext.Core.Windows\Plpext.Core.Windows.csproj", "{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}" | ||||||
|  | EndProject | ||||||
|  | Global | ||||||
|  | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||||
|  | 		Debug|Any CPU = Debug|Any CPU | ||||||
|  | 		Debug|x64 = Debug|x64 | ||||||
|  | 		Debug|x86 = Debug|x86 | ||||||
|  | 		Release|Any CPU = Release|Any CPU | ||||||
|  | 		Release|x64 = Release|x64 | ||||||
|  | 		Release|x86 = Release|x86 | ||||||
|  | 	EndGlobalSection | ||||||
|  | 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x64.ActiveCfg = Debug|x64 | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x64.Build.0 = Debug|x64 | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x86.ActiveCfg = Debug|x86 | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Debug|x86.Build.0 = Debug|x86 | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x64.ActiveCfg = Release|x64 | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x64.Build.0 = Release|x64 | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x86.ActiveCfg = Release|x86 | ||||||
|  | 		{CEC79B8A-12B4-4649-B859-08051B00FA96}.Release|x86.Build.0 = Release|x86 | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x64.ActiveCfg = Debug|x64 | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x64.Build.0 = Debug|x64 | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x86.ActiveCfg = Debug|x86 | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Debug|x86.Build.0 = Debug|x86 | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x64.ActiveCfg = Release|x64 | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x64.Build.0 = Release|x64 | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x86.ActiveCfg = Release|x86 | ||||||
|  | 		{2A0B3CBC-C79F-4B19-93AE-BCEEE44BDAD2}.Release|x86.Build.0 = Release|x86 | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Debug|x64.ActiveCfg = Debug|x64 | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Debug|x64.Build.0 = Debug|x64 | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Debug|x86.ActiveCfg = Debug|x86 | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Debug|x86.Build.0 = Debug|x86 | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Release|x64.ActiveCfg = Release|x64 | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Release|x64.Build.0 = Release|x64 | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Release|x86.ActiveCfg = Release|x86 | ||||||
|  | 		{CEF95E1F-8E07-4B32-A5D9-EE980AF5FBC4}.Release|x86.Build.0 = Release|x86 | ||||||
|  | 	EndGlobalSection | ||||||
|  | 	GlobalSection(SolutionProperties) = preSolution | ||||||
|  | 		HideSolutionNode = FALSE | ||||||
|  | 	EndGlobalSection | ||||||
|  | EndGlobal | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue