Added AvaloniaUI project.

This commit is contained in:
2025-03-01 17:03:06 -05:00
parent f78fe6fa2b
commit fc28004c89
25 changed files with 616 additions and 2 deletions

18
Harmonia.UI/App.axaml Normal file
View File

@@ -0,0 +1,18 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Harmonia.UI.ViewModels"
x:Class="Harmonia.UI.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</Application.Resources>
</Application>

62
Harmonia.UI/App.axaml.cs Normal file
View File

@@ -0,0 +1,62 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using Harmonia.Core.Extensions;
using Harmonia.UI.ViewModels;
using Harmonia.UI.Views;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Harmonia.UI;
public partial class App : Application
{
public static IServiceProvider ServiceProvider { get; private set; }
static App()
{
ServiceCollection services = new();
services.AddSingleton<MainViewModel>();
services.AddSingleton<MainWindow>();
services.AddSingleton<PlaybackBarViewModel>();
services.AddHarmonia();
ServiceProvider = services.BuildServiceProvider();
}
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
BindingPlugins.DataValidators.RemoveAt(0);
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
//desktop.MainWindow = new MainWindow
//{
// DataContext = new MainViewModel()
//};
desktop.MainWindow = ServiceProvider.GetRequiredService<MainWindow>();
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
//singleViewPlatform.MainView = new MainView
//{
// DataContext = new MainViewModel()
//};
singleViewPlatform.MainView = ServiceProvider.GetRequiredService<MainWindow>();
}
base.OnFrameworkInitializationCompleted();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Harmonia.Core\Harmonia.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Views\PlaybackBar.axaml.cs">
<DependentUpon>PlaybackBar.axaml</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
namespace Harmonia.UI.ViewModels;
public partial class MainViewModel : ViewModelBase
{
public string Greeting => "Welcome to Avalonia!";
}

View File

@@ -0,0 +1,178 @@
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using Harmonia.Core.Caching;
using Harmonia.Core.Imaging;
using Harmonia.Core.Models;
using Harmonia.Core.Player;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Harmonia.UI.ViewModels;
public partial class PlaybackBarViewModel : ViewModelBase
{
private readonly IAudioPlayer _audioPlayer;
private readonly IAudioImageCache _audioImageCache;
private bool _isPositionChangeInProgress;
private CancellationTokenSource? _audioImageCancellationTokenSource;
private Song? _song;
public Song? Song
{
get
{
return _song;
}
private set
{
_song = value;
OnPropertyChanged();
CurrentPosition = 0; // What if player is being loaded and returning to save state?
Position = 0;
MaxPosition = value?.Length.TotalSeconds ?? 0;
}
}
private Bitmap? _songImageSource;
public Bitmap? SongImageSource
{
get
{
return _songImageSource;
}
private set
{
_songImageSource = value;
OnPropertyChanged();
}
}
private double _currentPosition;
public double CurrentPosition
{
get
{
return _currentPosition;
}
set
{
_currentPosition = value;
OnPropertyChanged();
if (_isPositionChangeInProgress)
_audioPlayer.Position = value;
}
}
private double _position;
public double Position
{
get
{
return _position;
}
private set
{
_position = value;
OnPropertyChanged();
}
}
private double _maxPosition;
public double MaxPosition
{
get
{
return _maxPosition;
}
set
{
_maxPosition = value;
OnPropertyChanged();
}
}
public bool IsRandom
{
get
{
return _audioPlayer.IsRandom;
}
private set
{
_audioPlayer.IsRandom = value;
OnPropertyChanged();
}
}
public string Greeting => "Welcome to Harmonia!";
public PlaybackBarViewModel(IAudioPlayer audioPlayer, IAudioImageCache audioImageCache)
{
_audioPlayer = audioPlayer;
_audioPlayer.PlayingSongChanged += OnAudioPlayerPlayingSongChanged;
_audioImageCache = audioImageCache;
}
private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e)
{
Song = _audioPlayer.PlayingSong?.Song;
Task.Run(UpdateImage);
}
private async Task UpdateImage()
{
// TODO: Show default picture
if (Song == null)
return;
if (_audioImageCancellationTokenSource != null)
await _audioImageCancellationTokenSource.CancelAsync();
_audioImageCancellationTokenSource = new();
CancellationToken cancellationToken = _audioImageCancellationTokenSource.Token;
SongPictureInfo? songPictureInfo = await _audioImageCache.GetAsync(Song, cancellationToken);
if (songPictureInfo == null)
return;
await Dispatcher.UIThread.InvokeAsync(() => SetSongImageSource(songPictureInfo));
}
private void SetSongImageSource(SongPictureInfo songPictureInfo)
{
SongImageSource = new(songPictureInfo.Stream);
}
public void Play()
{
_audioPlayer.Play();
}
public void Pause()
{
_audioPlayer.Pause();
}
public void Stop()
{
_audioPlayer.Stop();
CurrentPosition = 0;
Position = 0;
}
public void Previous()
{
_audioPlayer.PreviousAsync();
}
public void Next()
{
_audioPlayer.NextAsync();
}
}

View File

@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Harmonia.UI.ViewModels;
public class ViewModelBase : ObservableObject
{
}

View File

@@ -0,0 +1,12 @@
using Microsoft.Extensions.DependencyInjection;
namespace Harmonia.UI.ViewModels;
public class ViewModelLocator
{
public static MainViewModel MainViewModel
=> App.ServiceProvider.GetRequiredService<MainViewModel>();
public static PlaybackBarViewModel PlaybackBarViewModel
=> App.ServiceProvider.GetRequiredService<PlaybackBarViewModel>();
}

View File

@@ -0,0 +1,22 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Harmonia.UI.ViewModels"
xmlns:views="clr-namespace:Harmonia.UI.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
DataContext="{x:Static vm:ViewModelLocator.MainViewModel}"
x:Class="Harmonia.UI.Views.MainView"
x:DataType="vm:MainViewModel">
<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:MainViewModel />
</Design.DataContext>
<StackPanel>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<views:PlaybackBar></views:PlaybackBar>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace Harmonia.UI.Views;
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,12 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Harmonia.UI.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:Harmonia.UI.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Harmonia.UI.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="Harmonia.UI">
<views:MainView />
</Window>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace Harmonia.UI.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,17 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Harmonia.UI.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
DataContext="{x:Static vm:ViewModelLocator.PlaybackBarViewModel}"
x:Class="Harmonia.UI.Views.PlaybackBar"
x:DataType="vm:PlaybackBarViewModel">
<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:MainViewModel />
</Design.DataContext>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace Harmonia.UI.Views;
public partial class PlaybackBar : UserControl
{
public PlaybackBar()
{
InitializeComponent();
}
}