mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2026-03-17 06:42:57 +00:00
feat: Implement lyrics download feature.
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<FluentTheme />
|
||||||
|
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
|
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
|
|||||||
@@ -242,4 +242,8 @@
|
|||||||
<data name="Common_Info" xml:space="preserve">
|
<data name="Common_Info" xml:space="preserve">
|
||||||
<value>Info</value>
|
<value>Info</value>
|
||||||
</data>
|
</data>
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<data name="Error_NoMusicFiles" xml:space="preserve">
|
||||||
|
<value>No music files found in the selected folder.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -242,4 +242,8 @@
|
|||||||
<data name="Common_Info" xml:space="preserve">
|
<data name="Common_Info" xml:space="preserve">
|
||||||
<value>提示</value>
|
<value>提示</value>
|
||||||
</data>
|
</data>
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<data name="Error_NoMusicFiles" xml:space="preserve">
|
||||||
|
<value>在所选文件夹中没有找到任何音乐文件。</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Avalonia;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
using ZonyLrcTools.Desktop.Views.Dialogs;
|
||||||
|
|
||||||
namespace ZonyLrcTools.Desktop.Services;
|
namespace ZonyLrcTools.Desktop.Services;
|
||||||
|
|
||||||
@@ -47,14 +48,18 @@ public class DialogService : IDialogService
|
|||||||
|
|
||||||
public async Task<bool> ShowConfirmDialogAsync(string title, string message)
|
public async Task<bool> ShowConfirmDialogAsync(string title, string message)
|
||||||
{
|
{
|
||||||
// TODO: Implement custom confirm dialog
|
if (MainWindow == null) return false;
|
||||||
await Task.CompletedTask;
|
|
||||||
return true;
|
var dialog = new ConfirmDialog(title, message);
|
||||||
|
var result = await dialog.ShowDialog<bool>(MainWindow);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ShowMessageAsync(string title, string message)
|
public async Task ShowMessageAsync(string title, string message)
|
||||||
{
|
{
|
||||||
// TODO: Implement custom message dialog
|
if (MainWindow == null) return;
|
||||||
await Task.CompletedTask;
|
|
||||||
|
var dialog = new MessageDialog(title, message);
|
||||||
|
await dialog.ShowDialog(MainWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using ZonyLrcTools.Common;
|
||||||
|
using ZonyLrcTools.Common.Configuration;
|
||||||
|
using ZonyLrcTools.Common.Infrastructure.IO;
|
||||||
|
using ZonyLrcTools.Common.Infrastructure.Threading;
|
||||||
using ZonyLrcTools.Common.Lyrics;
|
using ZonyLrcTools.Common.Lyrics;
|
||||||
|
using ZonyLrcTools.Common.TagInfo;
|
||||||
using ZonyLrcTools.Desktop.Infrastructure.Localization;
|
using ZonyLrcTools.Desktop.Infrastructure.Localization;
|
||||||
using ZonyLrcTools.Desktop.Services;
|
using ZonyLrcTools.Desktop.Services;
|
||||||
|
|
||||||
@@ -11,9 +18,14 @@ public partial class LyricsDownloadViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
private readonly ILyricsDownloader _lyricsDownloader;
|
private readonly ILyricsDownloader _lyricsDownloader;
|
||||||
private readonly IDialogService _dialogService;
|
private readonly IDialogService _dialogService;
|
||||||
|
private readonly IFileScanner _fileScanner;
|
||||||
|
private readonly ITagLoader _tagLoader;
|
||||||
|
private readonly GlobalOptions _options;
|
||||||
private readonly IUILocalizationService? _localization;
|
private readonly IUILocalizationService? _localization;
|
||||||
|
|
||||||
private CancellationTokenSource? _downloadCts;
|
private CancellationTokenSource? _downloadCts;
|
||||||
|
private Dictionary<string, MusicFileViewModel> _musicFileMap = new();
|
||||||
|
private List<MusicInfo> _scannedMusicInfos = new();
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string? _selectedFolderPath;
|
private string? _selectedFolderPath;
|
||||||
@@ -38,6 +50,21 @@ public partial class LyricsDownloadViewModel : ViewModelBase
|
|||||||
[NotifyPropertyChangedFor(nameof(CanStartDownload))]
|
[NotifyPropertyChangedFor(nameof(CanStartDownload))]
|
||||||
private bool _isDownloading;
|
private bool _isDownloading;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(StartDownloadCommand))]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(SelectFolderCommand))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(CanStartDownload))]
|
||||||
|
private bool _isScanning;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _scanProgressCount;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _scanTotalCount;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private double _scanProgressPercentage;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string? _currentProcessingFile;
|
private string? _currentProcessingFile;
|
||||||
|
|
||||||
@@ -49,6 +76,7 @@ public partial class LyricsDownloadViewModel : ViewModelBase
|
|||||||
public string LyricsParallel => _localization?["Lyrics_Parallel"] ?? "Parallel:";
|
public string LyricsParallel => _localization?["Lyrics_Parallel"] ?? "Parallel:";
|
||||||
public string LyricsStartDownload => _localization?["Lyrics_StartDownload"] ?? "Start Download";
|
public string LyricsStartDownload => _localization?["Lyrics_StartDownload"] ?? "Start Download";
|
||||||
public string LyricsStopDownload => _localization?["Lyrics_StopDownload"] ?? "Stop Download";
|
public string LyricsStopDownload => _localization?["Lyrics_StopDownload"] ?? "Stop Download";
|
||||||
|
public string LyricsScanning => _localization?["Lyrics_Status_Scanning"] ?? "Scanning files...";
|
||||||
public string CommonTotal => _localization?["Common_Total"] ?? "Total:";
|
public string CommonTotal => _localization?["Common_Total"] ?? "Total:";
|
||||||
public string CommonFiles => _localization?["Common_Files"] ?? "files";
|
public string CommonFiles => _localization?["Common_Files"] ?? "files";
|
||||||
public string CommonSuccess => _localization?["Common_Success"] ?? "Success:";
|
public string CommonSuccess => _localization?["Common_Success"] ?? "Success:";
|
||||||
@@ -58,17 +86,23 @@ public partial class LyricsDownloadViewModel : ViewModelBase
|
|||||||
public string ColumnFilePath => _localization?["Column_FilePath"] ?? "File Path";
|
public string ColumnFilePath => _localization?["Column_FilePath"] ?? "File Path";
|
||||||
public string ColumnStatus => _localization?["Column_Status"] ?? "Status";
|
public string ColumnStatus => _localization?["Column_Status"] ?? "Status";
|
||||||
|
|
||||||
public bool CanStartDownload => !IsDownloading && !string.IsNullOrEmpty(SelectedFolderPath);
|
public bool CanStartDownload => !IsDownloading && !IsScanning && MusicFiles.Count > 0;
|
||||||
|
|
||||||
public ObservableCollection<MusicFileViewModel> MusicFiles { get; } = new();
|
public ObservableCollection<MusicFileViewModel> MusicFiles { get; } = new();
|
||||||
|
|
||||||
public LyricsDownloadViewModel(
|
public LyricsDownloadViewModel(
|
||||||
ILyricsDownloader lyricsDownloader,
|
ILyricsDownloader lyricsDownloader,
|
||||||
IDialogService dialogService,
|
IDialogService dialogService,
|
||||||
|
IFileScanner fileScanner,
|
||||||
|
ITagLoader tagLoader,
|
||||||
|
IOptions<GlobalOptions> options,
|
||||||
IUILocalizationService? localization = null)
|
IUILocalizationService? localization = null)
|
||||||
{
|
{
|
||||||
_lyricsDownloader = lyricsDownloader;
|
_lyricsDownloader = lyricsDownloader;
|
||||||
_dialogService = dialogService;
|
_dialogService = dialogService;
|
||||||
|
_fileScanner = fileScanner;
|
||||||
|
_tagLoader = tagLoader;
|
||||||
|
_options = options.Value;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
|
|
||||||
if (_localization != null)
|
if (_localization != null)
|
||||||
@@ -86,6 +120,7 @@ public partial class LyricsDownloadViewModel : ViewModelBase
|
|||||||
OnPropertyChanged(nameof(LyricsParallel));
|
OnPropertyChanged(nameof(LyricsParallel));
|
||||||
OnPropertyChanged(nameof(LyricsStartDownload));
|
OnPropertyChanged(nameof(LyricsStartDownload));
|
||||||
OnPropertyChanged(nameof(LyricsStopDownload));
|
OnPropertyChanged(nameof(LyricsStopDownload));
|
||||||
|
OnPropertyChanged(nameof(LyricsScanning));
|
||||||
OnPropertyChanged(nameof(CommonTotal));
|
OnPropertyChanged(nameof(CommonTotal));
|
||||||
OnPropertyChanged(nameof(CommonFiles));
|
OnPropertyChanged(nameof(CommonFiles));
|
||||||
OnPropertyChanged(nameof(CommonSuccess));
|
OnPropertyChanged(nameof(CommonSuccess));
|
||||||
@@ -96,32 +131,155 @@ public partial class LyricsDownloadViewModel : ViewModelBase
|
|||||||
OnPropertyChanged(nameof(ColumnStatus));
|
OnPropertyChanged(nameof(ColumnStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand(CanExecute = nameof(CanSelectFolder))]
|
||||||
private async Task SelectFolderAsync()
|
private async Task SelectFolderAsync()
|
||||||
{
|
{
|
||||||
var folder = await _dialogService.ShowFolderPickerAsync("Select Music Folder");
|
var folder = await _dialogService.ShowFolderPickerAsync(
|
||||||
|
_localization?["Lyrics_SelectFolder"] ?? "Select Music Folder");
|
||||||
if (string.IsNullOrEmpty(folder)) return;
|
if (string.IsNullOrEmpty(folder)) return;
|
||||||
|
|
||||||
SelectedFolderPath = folder;
|
SelectedFolderPath = folder;
|
||||||
OnPropertyChanged(nameof(CanStartDownload));
|
await ScanFilesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanSelectFolder => !IsScanning && !IsDownloading;
|
||||||
|
|
||||||
|
private async Task ScanFilesAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(SelectedFolderPath)) return;
|
||||||
|
|
||||||
|
IsScanning = true;
|
||||||
|
MusicFiles.Clear();
|
||||||
|
_musicFileMap.Clear();
|
||||||
|
_scannedMusicInfos.Clear();
|
||||||
|
ScanProgressCount = 0;
|
||||||
|
ScanTotalCount = 0;
|
||||||
|
ScanProgressPercentage = 0;
|
||||||
|
TotalCount = 0;
|
||||||
|
CompletedCount = 0;
|
||||||
|
FailedCount = 0;
|
||||||
|
ProgressPercentage = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var files = (await _fileScanner.ScanMusicFilesAsync(SelectedFolderPath, _options.SupportFileExtensions))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (files.Count == 0)
|
||||||
|
{
|
||||||
|
await _dialogService.ShowMessageAsync(
|
||||||
|
_localization?["Common_Info"] ?? "Info",
|
||||||
|
_localization?["Error_NoMusicFiles"] ?? "No music files found in the selected folder.");
|
||||||
|
IsScanning = false;
|
||||||
|
OnPropertyChanged(nameof(CanStartDownload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScanTotalCount = files.Count;
|
||||||
|
|
||||||
|
using var warpTask = new WarpTask(ParallelCount);
|
||||||
|
var scanTasks = files.Select(file =>
|
||||||
|
warpTask.RunAsync(async () =>
|
||||||
|
{
|
||||||
|
var musicInfo = await _tagLoader.LoadTagAsync(file);
|
||||||
|
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
if (musicInfo != null &&
|
||||||
|
(!string.IsNullOrEmpty(musicInfo.Name) || !string.IsNullOrEmpty(musicInfo.Artist)))
|
||||||
|
{
|
||||||
|
var vm = new MusicFileViewModel
|
||||||
|
{
|
||||||
|
FilePath = musicInfo.FilePath,
|
||||||
|
Name = musicInfo.Name,
|
||||||
|
Artist = musicInfo.Artist,
|
||||||
|
StatusMessage = _localization?["Status_Pending"] ?? "Pending"
|
||||||
|
};
|
||||||
|
MusicFiles.Add(vm);
|
||||||
|
_musicFileMap[musicInfo.FilePath] = vm;
|
||||||
|
_scannedMusicInfos.Add(musicInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScanProgressCount++;
|
||||||
|
ScanProgressPercentage = ScanProgressCount * 100.0 / ScanTotalCount;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Task.WhenAll(scanTasks);
|
||||||
|
|
||||||
|
TotalCount = MusicFiles.Count;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _dialogService.ShowMessageAsync(
|
||||||
|
_localization?["Common_Error"] ?? "Error",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsScanning = false;
|
||||||
|
OnPropertyChanged(nameof(CanStartDownload));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanStartDownload))]
|
[RelayCommand(CanExecute = nameof(CanStartDownload))]
|
||||||
private async Task StartDownloadAsync()
|
private async Task StartDownloadAsync()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SelectedFolderPath)) return;
|
if (_scannedMusicInfos.Count == 0) return;
|
||||||
|
|
||||||
IsDownloading = true;
|
IsDownloading = true;
|
||||||
CompletedCount = 0;
|
CompletedCount = 0;
|
||||||
FailedCount = 0;
|
FailedCount = 0;
|
||||||
ProgressPercentage = 0;
|
ProgressPercentage = 0;
|
||||||
|
TotalCount = _scannedMusicInfos.Count;
|
||||||
|
|
||||||
|
// Reset all row statuses
|
||||||
|
foreach (var vm in MusicFiles)
|
||||||
|
{
|
||||||
|
vm.IsProcessed = false;
|
||||||
|
vm.IsSuccessful = false;
|
||||||
|
vm.StatusMessage = _localization?["Status_Pending"] ?? "Pending";
|
||||||
|
}
|
||||||
|
|
||||||
_downloadCts = new CancellationTokenSource();
|
_downloadCts = new CancellationTokenSource();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// TODO: Implement actual download logic using _lyricsDownloader
|
await _lyricsDownloader.DownloadAsync(
|
||||||
await Task.Delay(1000, _downloadCts.Token); // Placeholder
|
_scannedMusicInfos,
|
||||||
|
ParallelCount,
|
||||||
|
async musicInfo =>
|
||||||
|
{
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
if (_musicFileMap.TryGetValue(musicInfo.FilePath, out var vm))
|
||||||
|
{
|
||||||
|
vm.IsProcessed = true;
|
||||||
|
vm.IsSuccessful = musicInfo.IsSuccessful;
|
||||||
|
|
||||||
|
if (musicInfo.IsPruneMusic)
|
||||||
|
{
|
||||||
|
vm.StatusMessage = _localization?["Status_Skipped"] ?? "Skipped";
|
||||||
|
}
|
||||||
|
else if (musicInfo.IsSuccessful)
|
||||||
|
{
|
||||||
|
vm.StatusMessage = _localization?["Status_Success"] ?? "Success";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vm.StatusMessage = _localization?["Status_Failed"] ?? "Failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (musicInfo.IsSuccessful)
|
||||||
|
CompletedCount++;
|
||||||
|
else
|
||||||
|
FailedCount++;
|
||||||
|
|
||||||
|
ProgressPercentage = (CompletedCount + FailedCount) * 100.0 / TotalCount;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_downloadCts.Token);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|||||||
39
src/ZonyLrcTools.Desktop/Views/Dialogs/ConfirmDialog.axaml
Normal file
39
src/ZonyLrcTools.Desktop/Views/Dialogs/ConfirmDialog.axaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="ZonyLrcTools.Desktop.Views.Dialogs.ConfirmDialog"
|
||||||
|
Title=""
|
||||||
|
Width="400"
|
||||||
|
Height="200"
|
||||||
|
CanResize="False"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
SizeToContent="Height">
|
||||||
|
|
||||||
|
<Grid Margin="24" RowDefinitions="Auto,*,Auto">
|
||||||
|
<!-- Title -->
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
x:Name="TitleText"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Margin="0,0,0,12" />
|
||||||
|
|
||||||
|
<!-- Message -->
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
x:Name="MessageText"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Margin="0,0,0,20" />
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Spacing="8">
|
||||||
|
<Button x:Name="CancelButton"
|
||||||
|
MinWidth="80"
|
||||||
|
Click="OnCancelClick" />
|
||||||
|
<Button x:Name="ConfirmButton"
|
||||||
|
MinWidth="80"
|
||||||
|
Classes="accent"
|
||||||
|
Click="OnConfirmClick" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ZonyLrcTools.Desktop.Infrastructure.Localization;
|
||||||
|
|
||||||
|
namespace ZonyLrcTools.Desktop.Views.Dialogs;
|
||||||
|
|
||||||
|
public partial class ConfirmDialog : Window
|
||||||
|
{
|
||||||
|
public ConfirmDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfirmDialog(string title, string message) : this()
|
||||||
|
{
|
||||||
|
TitleText.Text = title;
|
||||||
|
MessageText.Text = message;
|
||||||
|
Title = title;
|
||||||
|
|
||||||
|
var localization = App.Services?.GetService<IUILocalizationService>();
|
||||||
|
ConfirmButton.Content = localization?["Common_OK"] ?? "OK";
|
||||||
|
CancelButton.Content = localization?["Common_Cancel"] ?? "Cancel";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfirmClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCancelClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/ZonyLrcTools.Desktop/Views/Dialogs/MessageDialog.axaml
Normal file
32
src/ZonyLrcTools.Desktop/Views/Dialogs/MessageDialog.axaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="ZonyLrcTools.Desktop.Views.Dialogs.MessageDialog"
|
||||||
|
Title=""
|
||||||
|
Width="400"
|
||||||
|
Height="200"
|
||||||
|
CanResize="False"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
SizeToContent="Height">
|
||||||
|
|
||||||
|
<Grid Margin="24" RowDefinitions="Auto,*,Auto">
|
||||||
|
<!-- Title -->
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
x:Name="TitleText"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Margin="0,0,0,12" />
|
||||||
|
|
||||||
|
<!-- Message -->
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
x:Name="MessageText"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Margin="0,0,0,20" />
|
||||||
|
|
||||||
|
<!-- OK Button -->
|
||||||
|
<Button Grid.Row="2"
|
||||||
|
x:Name="OkButton"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
MinWidth="80"
|
||||||
|
Click="OnOkClick" />
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ZonyLrcTools.Desktop.Infrastructure.Localization;
|
||||||
|
|
||||||
|
namespace ZonyLrcTools.Desktop.Views.Dialogs;
|
||||||
|
|
||||||
|
public partial class MessageDialog : Window
|
||||||
|
{
|
||||||
|
public MessageDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageDialog(string title, string message) : this()
|
||||||
|
{
|
||||||
|
TitleText.Text = title;
|
||||||
|
MessageText.Text = message;
|
||||||
|
Title = title;
|
||||||
|
|
||||||
|
var localization = App.Services?.GetService<IUILocalizationService>();
|
||||||
|
OkButton.Content = localization?["Common_OK"] ?? "OK";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOkClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
<UserControl 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="using:ZonyLrcTools.Desktop.ViewModels"
|
xmlns:vm="using:ZonyLrcTools.Desktop.ViewModels"
|
||||||
|
xmlns:loc="using:ZonyLrcTools.Desktop.Infrastructure.Localization"
|
||||||
x:Class="ZonyLrcTools.Desktop.Views.Pages.LyricsDownloadPage"
|
x:Class="ZonyLrcTools.Desktop.Views.Pages.LyricsDownloadPage"
|
||||||
x:DataType="vm:LyricsDownloadViewModel">
|
x:DataType="vm:LyricsDownloadViewModel">
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
<Button Command="{Binding StartDownloadCommand}"
|
<Button Command="{Binding StartDownloadCommand}"
|
||||||
Content="{Binding LyricsStartDownload}"
|
Content="{Binding LyricsStartDownload}"
|
||||||
IsVisible="{Binding !IsDownloading}"
|
IsVisible="{Binding !IsDownloading}"
|
||||||
|
IsEnabled="{Binding CanStartDownload}"
|
||||||
Classes="accent"
|
Classes="accent"
|
||||||
MinWidth="100" />
|
MinWidth="100" />
|
||||||
<Button Command="{Binding CancelDownloadCommand}"
|
<Button Command="{Binding CancelDownloadCommand}"
|
||||||
@@ -65,7 +67,21 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Progress Bar (visible during download) -->
|
<!-- Progress Bar: Scanning phase -->
|
||||||
|
<StackPanel IsVisible="{Binding IsScanning}" Spacing="8">
|
||||||
|
<ProgressBar Value="{Binding ScanProgressPercentage}"
|
||||||
|
Maximum="100"
|
||||||
|
Height="6" />
|
||||||
|
<TextBlock HorizontalAlignment="Center" FontSize="12" Opacity="0.8">
|
||||||
|
<Run Text="{Binding LyricsScanning}" />
|
||||||
|
<Run Text=" " />
|
||||||
|
<Run Text="{Binding ScanProgressCount}" />
|
||||||
|
<Run Text=" / " />
|
||||||
|
<Run Text="{Binding ScanTotalCount}" />
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Progress Bar: Download phase -->
|
||||||
<StackPanel IsVisible="{Binding IsDownloading}" Spacing="8">
|
<StackPanel IsVisible="{Binding IsDownloading}" Spacing="8">
|
||||||
<ProgressBar Value="{Binding ProgressPercentage}"
|
<ProgressBar Value="{Binding ProgressPercentage}"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
@@ -90,20 +106,25 @@
|
|||||||
GridLinesVisibility="Horizontal"
|
GridLinesVisibility="Horizontal"
|
||||||
BorderThickness="0">
|
BorderThickness="0">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn Header="{Binding ColumnSongName}"
|
<DataGridTextColumn x:DataType="vm:MusicFileViewModel"
|
||||||
|
Header="{loc:Localize Column_SongName}"
|
||||||
Binding="{Binding Name}"
|
Binding="{Binding Name}"
|
||||||
Width="*" />
|
Width="*" />
|
||||||
<DataGridTextColumn Header="{Binding ColumnArtist}"
|
<DataGridTextColumn x:DataType="vm:MusicFileViewModel"
|
||||||
|
Header="{loc:Localize Column_Artist}"
|
||||||
Binding="{Binding Artist}"
|
Binding="{Binding Artist}"
|
||||||
Width="150" />
|
Width="150" />
|
||||||
<DataGridTextColumn Header="{Binding ColumnFilePath}"
|
<DataGridTextColumn x:DataType="vm:MusicFileViewModel"
|
||||||
|
Header="{loc:Localize Column_FilePath}"
|
||||||
Binding="{Binding FilePath}"
|
Binding="{Binding FilePath}"
|
||||||
Width="250" />
|
Width="250" />
|
||||||
<DataGridTextColumn Header="{Binding ColumnStatus}"
|
<DataGridTextColumn x:DataType="vm:MusicFileViewModel"
|
||||||
|
Header="{loc:Localize Column_Status}"
|
||||||
Binding="{Binding StatusMessage}"
|
Binding="{Binding StatusMessage}"
|
||||||
Width="100" />
|
Width="100" />
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Status Bar -->
|
<!-- Status Bar -->
|
||||||
|
|||||||
Reference in New Issue
Block a user