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
|
||||
*DS_Store
|
||||
*.sln.ide
|
||||
/build/win
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x86;x64</Platforms>
|
||||
<Platforms>x86;x64;AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Plpext.Core\Plpext.Core.csproj" />
|
||||
</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">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>OpenAL32.dll</TargetPath>
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace Plpext.Core.AudioConverter
|
|||
|
||||
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)
|
||||
|
|
|
@ -26,11 +26,10 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
|||
|
||||
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;
|
||||
|
||||
|
||||
_playbackCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
try
|
||||
{
|
||||
|
@ -42,7 +41,9 @@ public sealed class AudioPlayer : IAudioPlayer, IDisposable
|
|||
|
||||
AL.BufferData(bufferId, ALFormat.Mono16, input.Data.Span, input.Frequency);
|
||||
AL.Source(sourceId, ALSourcei.Buffer, bufferId);
|
||||
if(autoStart)
|
||||
return await Start();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Plpext.Core.Interfaces;
|
|||
|
||||
public interface IAudioPlayer
|
||||
{
|
||||
Task<bool> InitAudioPlayerAsync(AudioFile input, CancellationToken cancellationToken);
|
||||
Task<bool> InitAudioPlayerAsync(AudioFile input, bool autoStart, CancellationToken cancellationToken);
|
||||
void Resume();
|
||||
void Pause();
|
||||
void Stop();
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace Plpext.Core.Models
|
|||
public record AudioFile
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public ReadOnlyMemory<byte> MP3Data { get; init; }
|
||||
public ReadOnlyMemory<byte> Data { get; init; }
|
||||
public TimeSpan Duration { 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…
Reference in a new issue