Adding initial playback bar logic.
This commit is contained in:
@@ -13,9 +13,12 @@ public abstract class FileRepository<TObject> : IRepository<TObject> where TObje
|
|||||||
|
|
||||||
public FileRepository()
|
public FileRepository()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(DirectoryName) || Directory.Exists(DirectoryName) == false)
|
if (string.IsNullOrWhiteSpace(DirectoryName))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (Directory.Exists(DirectoryName) == false)
|
||||||
|
Directory.CreateDirectory(DirectoryName);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(Extension))
|
if (string.IsNullOrWhiteSpace(Extension))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ public abstract class FileRepository<TObject> : IRepository<TObject> where TObje
|
|||||||
|
|
||||||
var fileInfoList = directoryInfo.EnumerateFiles("*." + Extension, SearchOption.TopDirectoryOnly).Where(x => x.Attributes.HasFlag(FileAttributes.Hidden) == false);
|
var fileInfoList = directoryInfo.EnumerateFiles("*." + Extension, SearchOption.TopDirectoryOnly).Where(x => x.Attributes.HasFlag(FileAttributes.Hidden) == false);
|
||||||
|
|
||||||
return fileInfoList.Select(fileInfo => fileInfo.FullName).ToList();
|
return [.. fileInfoList.Select(fileInfo => fileInfo.FullName)];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadFileNamesIntoMap(List<string> fileNames)
|
private void LoadFileNamesIntoMap(List<string> fileNames)
|
||||||
|
|||||||
@@ -74,12 +74,17 @@ public class AudioImageExtractor : IAudioImageExtractor
|
|||||||
|
|
||||||
private string? GetImagePath(string path)
|
private string? GetImagePath(string path)
|
||||||
{
|
{
|
||||||
|
string? directoryName = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(directoryName))
|
||||||
|
return null;
|
||||||
|
|
||||||
string[] fileNames;
|
string[] fileNames;
|
||||||
string extension;
|
string extension;
|
||||||
|
|
||||||
foreach (string searchPattern in _searchPatterns)
|
foreach (string searchPattern in _searchPatterns)
|
||||||
{
|
{
|
||||||
fileNames = GetFilesFromDirectory(path, searchPattern);
|
fileNames = GetFilesFromDirectory(directoryName, searchPattern);
|
||||||
|
|
||||||
foreach (string fileName in fileNames)
|
foreach (string fileName in fileNames)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,4 +5,9 @@ namespace Harmonia.Core.Playlists;
|
|||||||
public interface IPlaylistRepository : IRepository<Playlist>
|
public interface IPlaylistRepository : IRepository<Playlist>
|
||||||
{
|
{
|
||||||
Playlist? GetPlaylist(PlaylistSong playlistSong);
|
Playlist? GetPlaylist(PlaylistSong playlistSong);
|
||||||
|
void AddPlaylist();
|
||||||
|
void RemovePlaylist(Playlist playlist);
|
||||||
|
|
||||||
|
event EventHandler<PlaylistAddedEventArgs> PlaylistAdded;
|
||||||
|
event EventHandler<PlaylistRemovedEventArgs> PlaylistRemoved;
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ public class Playlist
|
|||||||
{
|
{
|
||||||
public string UID { get; init; } = Guid.NewGuid().ToString();
|
public string UID { get; init; } = Guid.NewGuid().ToString();
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
public List<PlaylistSong> Songs { get; } = [];
|
public List<PlaylistSong> Songs { get; init; } = []; // TODO: Change to "private init" once deserialization is fixed
|
||||||
public List<GroupOption> GroupOptions { get; set; } = [];
|
public List<GroupOption> GroupOptions { get; set; } = [];
|
||||||
public List<SortOption> SortOptions { get; set; } = [];
|
public List<SortOption> SortOptions { get; set; } = [];
|
||||||
public bool IsLocked { get; set; }
|
public bool IsLocked { get; set; }
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using Harmonia.Core.Data;
|
namespace Harmonia.Core.Playlists;
|
||||||
|
|
||||||
namespace Harmonia.Core.Playlists;
|
public class PlaylistManager(IPlaylistRepository playlistRepository) : IPlaylistManager
|
||||||
|
|
||||||
public class PlaylistManager(IRepository<Playlist> playlistRepository) : IPlaylistManager
|
|
||||||
{
|
{
|
||||||
private Playlist? _currentPlaylist;
|
private Playlist? _currentPlaylist;
|
||||||
public Playlist? CurrentPlaylist
|
public Playlist? CurrentPlaylist
|
||||||
|
|||||||
@@ -4,7 +4,26 @@ namespace Harmonia.Core.Playlists;
|
|||||||
|
|
||||||
public class PlaylistRepository : JsonFileRepository<Playlist>, IPlaylistRepository
|
public class PlaylistRepository : JsonFileRepository<Playlist>, IPlaylistRepository
|
||||||
{
|
{
|
||||||
protected override string DirectoryName => string.Empty;
|
protected override string DirectoryName => Path.Combine("Playlists");
|
||||||
|
|
||||||
|
public PlaylistRepository()
|
||||||
|
{
|
||||||
|
List<Playlist> playlists = Get();
|
||||||
|
|
||||||
|
foreach (Playlist playlist in playlists)
|
||||||
|
{
|
||||||
|
playlist.PlaylistUpdated += OnPlaylistUpdated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not Playlist playlist)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Save(playlist);
|
||||||
|
//PlaylistUpdated?.Invoke(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
public Playlist? GetPlaylist(PlaylistSong playlistSong)
|
public Playlist? GetPlaylist(PlaylistSong playlistSong)
|
||||||
{
|
{
|
||||||
@@ -24,4 +43,29 @@ public class PlaylistRepository : JsonFileRepository<Playlist>, IPlaylistReposit
|
|||||||
|
|
||||||
throw new Exception("Unable to determine new fileName");
|
throw new Exception("Unable to determine new fileName");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event EventHandler<PlaylistAddedEventArgs>? PlaylistAdded;
|
||||||
|
//public event EventHandler<PlaylistUpdatedEventArgs>? PlaylistUpdated;
|
||||||
|
public event EventHandler<PlaylistRemovedEventArgs>? PlaylistRemoved;
|
||||||
|
|
||||||
|
public void AddPlaylist()
|
||||||
|
{
|
||||||
|
Playlist playlist = new()
|
||||||
|
{
|
||||||
|
Name = "New Playlist"
|
||||||
|
};
|
||||||
|
|
||||||
|
playlist.PlaylistUpdated += OnPlaylistUpdated;
|
||||||
|
|
||||||
|
Save(playlist);
|
||||||
|
PlaylistAdded?.Invoke(this, new(playlist));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePlaylist(Playlist playlist)
|
||||||
|
{
|
||||||
|
playlist.PlaylistUpdated -= OnPlaylistUpdated;
|
||||||
|
|
||||||
|
Delete(playlist);
|
||||||
|
PlaylistRemoved?.Invoke(this, new(playlist));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,5 +5,5 @@ namespace Harmonia.Core.Playlists;
|
|||||||
public class PlaylistSong(Song song)
|
public class PlaylistSong(Song song)
|
||||||
{
|
{
|
||||||
public string UID { get; } = Guid.NewGuid().ToString();
|
public string UID { get; } = Guid.NewGuid().ToString();
|
||||||
public Song Song { get; } = song;
|
public Song Song { get; init; } = song;
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:vm="clr-namespace:Harmonia.UI.ViewModels"
|
xmlns:vm="clr-namespace:Harmonia.UI.ViewModels"
|
||||||
|
xmlns:control="clr-namespace:Harmonia.UI.Controls"
|
||||||
|
xmlns:semi="https://irihi.tech/semi"
|
||||||
x:Class="Harmonia.UI.App"
|
x:Class="Harmonia.UI.App"
|
||||||
RequestedThemeVariant="Default">
|
RequestedThemeVariant="Default">
|
||||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<!--<FluentTheme />-->
|
||||||
|
<semi:SemiTheme Locale="en-US"/>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
|
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
|
|||||||
32
Harmonia.UI/Converters/ArtistsToStringConverter.cs
Normal file
32
Harmonia.UI/Converters/ArtistsToStringConverter.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Harmonia.UI.Converters;
|
||||||
|
|
||||||
|
public class ArtistsToStringConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public ArtistsToStringConverter()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is not List<string>)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
List<string> artists = (List<string>)value;
|
||||||
|
|
||||||
|
if (artists == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return string.Join(" / ", artists);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Harmonia.UI/Converters/NullVisibilityConverter.cs
Normal file
29
Harmonia.UI/Converters/NullVisibilityConverter.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Harmonia.UI.Converters;
|
||||||
|
|
||||||
|
public sealed class NullVisibilityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public NullVisibilityConverter()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is IList list)
|
||||||
|
{
|
||||||
|
return list.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value is not null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Harmonia.UI/Converters/SecondsToStringConverter.cs
Normal file
37
Harmonia.UI/Converters/SecondsToStringConverter.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Harmonia.UI.Converters;
|
||||||
|
|
||||||
|
public sealed class SecondsToStringConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public SecondsToStringConverter()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is not double doubleValue)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
TimeSpan timeSpan = TimeSpan.FromSeconds(doubleValue);
|
||||||
|
|
||||||
|
if (timeSpan.Hours >= 1)
|
||||||
|
return timeSpan.ToString(@"%h\:mm\:ss");
|
||||||
|
|
||||||
|
return timeSpan.ToString(@"%m\:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (value is not string stringValue)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return TimeSpan.Parse(stringValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Harmonia.UI/Converters/SongTitleConverter.cs
Normal file
27
Harmonia.UI/Converters/SongTitleConverter.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Harmonia.Core.Models;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Harmonia.UI.Converters;
|
||||||
|
|
||||||
|
public sealed class SongTitleConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public SongTitleConverter()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is not Song song)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(song.Title) ? song.ShortFileName : song.Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,14 +11,15 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
|
<PackageReference Include="Avalonia" Version="11.2.5" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.5" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.5" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--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 Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
|
||||||
|
<PackageReference Include="Semi.Avalonia" Version="11.2.1.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Harmonia.Core.Caching;
|
using Harmonia.Core.Caching;
|
||||||
using Harmonia.Core.Imaging;
|
using Harmonia.Core.Imaging;
|
||||||
using Harmonia.Core.Models;
|
using Harmonia.Core.Models;
|
||||||
using Harmonia.Core.Player;
|
using Harmonia.Core.Player;
|
||||||
|
using Harmonia.Core.Playlists;
|
||||||
|
using Harmonia.Core.Scanner;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -14,8 +18,8 @@ public partial class PlaybackBarViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
private readonly IAudioPlayer _audioPlayer;
|
private readonly IAudioPlayer _audioPlayer;
|
||||||
private readonly IAudioImageCache _audioImageCache;
|
private readonly IAudioImageCache _audioImageCache;
|
||||||
|
private readonly DispatcherTimer _timer;
|
||||||
|
|
||||||
private bool _isPositionChangeInProgress;
|
|
||||||
private CancellationTokenSource? _audioImageCancellationTokenSource;
|
private CancellationTokenSource? _audioImageCancellationTokenSource;
|
||||||
|
|
||||||
private Song? _song;
|
private Song? _song;
|
||||||
@@ -95,6 +99,17 @@ public partial class PlaybackBarViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _isPositionChangeInProgress;
|
||||||
|
public bool IsPositionChangeInProgress
|
||||||
|
{
|
||||||
|
get { return _isPositionChangeInProgress; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isPositionChangeInProgress = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsRandom
|
public bool IsRandom
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -110,12 +125,14 @@ public partial class PlaybackBarViewModel : ViewModelBase
|
|||||||
|
|
||||||
public string Greeting => "Welcome to Harmonia!";
|
public string Greeting => "Welcome to Harmonia!";
|
||||||
|
|
||||||
public PlaybackBarViewModel(IAudioPlayer audioPlayer, IAudioImageCache audioImageCache)
|
public PlaybackBarViewModel(IAudioPlayer audioPlayer, IAudioImageCache audioImageCache, IPlaylistRepository playlistRepository, IAudioFileScanner audioFileScanner)
|
||||||
{
|
{
|
||||||
_audioPlayer = audioPlayer;
|
_audioPlayer = audioPlayer;
|
||||||
_audioPlayer.PlayingSongChanged += OnAudioPlayerPlayingSongChanged;
|
_audioPlayer.PlayingSongChanged += OnAudioPlayerPlayingSongChanged;
|
||||||
|
|
||||||
_audioImageCache = audioImageCache;
|
_audioImageCache = audioImageCache;
|
||||||
|
|
||||||
|
_timer = new(TimeSpan.FromMilliseconds(100), DispatcherPriority.Default, TickTock);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e)
|
private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e)
|
||||||
@@ -149,6 +166,16 @@ public partial class PlaybackBarViewModel : ViewModelBase
|
|||||||
SongImageSource = new(songPictureInfo.Stream);
|
SongImageSource = new(songPictureInfo.Stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TickTock(object? sender, object e)
|
||||||
|
{
|
||||||
|
Position = _audioPlayer.Position;
|
||||||
|
|
||||||
|
if (IsPositionChangeInProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CurrentPosition = _audioPlayer.Position;
|
||||||
|
}
|
||||||
|
|
||||||
public void Play()
|
public void Play()
|
||||||
{
|
{
|
||||||
_audioPlayer.Play();
|
_audioPlayer.Play();
|
||||||
|
|||||||
@@ -3,15 +3,91 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="clr-namespace:Harmonia.UI.ViewModels"
|
xmlns:vm="clr-namespace:Harmonia.UI.ViewModels"
|
||||||
|
xmlns:converter="clr-namespace:Harmonia.UI.Converters"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
DataContext="{x:Static vm:ViewModelLocator.PlaybackBarViewModel}"
|
DataContext="{x:Static vm:ViewModelLocator.PlaybackBarViewModel}"
|
||||||
x:Class="Harmonia.UI.Views.PlaybackBar"
|
x:Class="Harmonia.UI.Views.PlaybackBar"
|
||||||
x:DataType="vm:PlaybackBarViewModel">
|
x:DataType="vm:PlaybackBarViewModel">
|
||||||
<Design.DataContext>
|
<UserControl.Resources>
|
||||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
<converter:SecondsToStringConverter x:Key="SecondsToString" />
|
||||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
<converter:ArtistsToStringConverter x:Key="ArtistsToString" />
|
||||||
<vm:MainViewModel />
|
<converter:NullVisibilityConverter x:Key="NullVisibility" />
|
||||||
</Design.DataContext>
|
<converter:SongTitleConverter x:Key="SongTitle" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition></ColumnDefinition>
|
||||||
|
<ColumnDefinition></ColumnDefinition>
|
||||||
|
<ColumnDefinition></ColumnDefinition>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"></RowDefinition>
|
||||||
|
<RowDefinition></RowDefinition>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
<!-- Slider -->
|
||||||
|
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" VerticalAlignment="Center" Margin="0 0 0 10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||||
|
<ColumnDefinition></ColumnDefinition>
|
||||||
|
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"></RowDefinition>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock Grid.Column="0" Foreground="#aaaaaa" Text="{Binding Position, Converter={StaticResource SecondsToString}}" FontSize="13" Margin="0 0 6 0" VerticalAlignment="Center"></TextBlock>
|
||||||
|
<Slider Name="TrackSlider" Loaded="Slider_Loaded" Grid.Column="1" Value="{Binding CurrentPosition, Mode=TwoWay, UpdateSourceTrigger=Explicit}" Minimum="0" Maximum="{Binding MaxPosition, Mode=OneWay}" VerticalAlignment="Center" />
|
||||||
|
<TextBlock Grid.Column="2" Foreground="#aaaaaa" Text="{Binding MaxPosition, Converter={StaticResource SecondsToString}}" FontSize="13" Margin="6 0 0 0" VerticalAlignment="Center"></TextBlock>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Song Info -->
|
||||||
|
<Grid Grid.Row="1" Grid.Column="0" Name="PlayingSongGrid">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Grid Margin="0 0 10 0">
|
||||||
|
<!--<Image Name="PlayingSongImage" Width="80" Height="80"></Image>-->
|
||||||
|
<Image Source="{Binding SongImageSource, Mode=TwoWay}" Width="80" Height="80"></Image>
|
||||||
|
<Canvas Background="#19000000"></Canvas>
|
||||||
|
</Grid>
|
||||||
|
<StackPanel VerticalAlignment="Center">
|
||||||
|
<TextBlock Foreground="#dddddd" FontWeight="SemiBold" FontSize="16" LineHeight="20" Text="{Binding Song, Converter={StaticResource SongTitle}}"></TextBlock>
|
||||||
|
<TextBlock Foreground="#aaaaaa" FontSize="14" LineHeight="20" Text="{Binding Song.Artists, Converter={StaticResource ArtistsToString}}"></TextBlock>
|
||||||
|
<TextBlock Foreground="#aaaaaa" FontSize="14" LineHeight="20" Text="{Binding Song.Album}"></TextBlock>
|
||||||
|
<TextBlock Foreground="#777" FontSize="12" IsVisible="{Binding Song, Converter={StaticResource NullVisibility}}" FontWeight="Normal" Opacity="1.0">
|
||||||
|
<Run Text="{Binding Song.FileType}"></Run>
|
||||||
|
<Run Text=" - "></Run>
|
||||||
|
<Run Text="{Binding Song.BitRate}"></Run>
|
||||||
|
<Run Text=" kbps - "></Run>
|
||||||
|
<Run Text="{Binding Song.SampleRate}"></Run>
|
||||||
|
<Run Text=" Hz"></Run>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<Grid Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Button Margin="8 0 8 0" Click="PreviousSongButton_Click" Theme="{StaticResource BorderlessButton}">
|
||||||
|
<Button.Content>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<PathIcon Data="{StaticResource SemiIconBackward}" Margin="0 0 0 0"></PathIcon>
|
||||||
|
<TextBlock Text="" FontSize="18"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button.Content>
|
||||||
|
</Button>
|
||||||
|
<Button Margin="8 0 8 0" Click="StopButton_Click">
|
||||||
|
<TextBlock Text="B" FontSize="18"></TextBlock>
|
||||||
|
</Button>
|
||||||
|
<Button Margin="8 0 8 0" Click="PlayButton_Click">
|
||||||
|
<TextBlock Text="A" FontSize="36"></TextBlock>
|
||||||
|
</Button>
|
||||||
|
<Button Margin="8 0 8 0" Click="PauseButton_Click">
|
||||||
|
<TextBlock Text="C" FontSize="18"></TextBlock>
|
||||||
|
</Button>
|
||||||
|
<Button Margin="8 0 8 0" Click="NextSongButton_Click">
|
||||||
|
<TextBlock Text="E" FontSize="18"></TextBlock>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -1,11 +1,73 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Harmonia.UI.ViewModels;
|
||||||
|
|
||||||
namespace Harmonia.UI.Views;
|
namespace Harmonia.UI.Views;
|
||||||
|
|
||||||
public partial class PlaybackBar : UserControl
|
public partial class PlaybackBar : UserControl
|
||||||
{
|
{
|
||||||
|
private readonly PlaybackBarViewModel _viewModel;
|
||||||
|
|
||||||
public PlaybackBar()
|
public PlaybackBar()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_viewModel = (PlaybackBarViewModel)DataContext!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Slider_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not Slider slider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
slider.AddHandler(PointerPressedEvent, OnSliderPointerPressed, RoutingStrategies.Tunnel);
|
||||||
|
slider.AddHandler(PointerReleasedEvent, OnSliderPointerReleased, RoutingStrategies.Tunnel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSliderPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not Slider slider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PointerPoint currentPoint = e.GetCurrentPoint(slider);
|
||||||
|
|
||||||
|
if (currentPoint.Properties.IsLeftButtonPressed == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_viewModel.IsPositionChangeInProgress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSliderPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not Slider slider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_viewModel.CurrentPosition = slider.Value;
|
||||||
|
_viewModel.IsPositionChangeInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_viewModel.Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_viewModel.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PauseButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_viewModel.Pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PreviousSongButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_viewModel.Previous();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NextSongButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_viewModel.Next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user