fix: 扫描时界面会因为文件名不同而抖动的问题
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# AGENTS.md
|
||||
|
||||
## AI 行为准则
|
||||
|
||||
- **称呼规范**:始终称呼用户为「思谦」或「王思谦」。
|
||||
- **信息缺失处理**:遇到任何不确定、拿不准或信息缺失的情况,不要自行推测。先收集整理好问题,停下来向思谦确认后再继续。
|
||||
|
||||
## Build & Test
|
||||
|
||||
```bash
|
||||
@@ -26,23 +31,14 @@ dotnet run --project src/GUI # Launch the Avalonia de
|
||||
|
||||
## Known Issues / Gotchas
|
||||
|
||||
|
||||
- 扫描时如果报错,报错信息可能会填满整个窗口,导致Overlay无法关闭。
|
||||
- 读取时如果报错,没有任何警告,会静默报错。需要有一个界面右下方的toast,或是发送系统通知告知用户遇到了错误。
|
||||
- 扫描界面的宽度会因为文件名而发生变化。固定为窗口宽度的50%。如果文件名超过这个宽度则省略中间的字符,仅保留开头以及最后10个字符。
|
||||
- **升级 Avalonia 版本时,必须同步更新所有次级包引用。** 仅修改主包 `Avalonia` 的 Version 不会自动更新 `Avalonia.Desktop`、`Avalonia.Themes.Fluent`、`Avalonia.Fonts.Inter` 等次级包。必须手动逐个修改,然后执行 `dotnet clean && dotnet restore`。否则运行时的原生库(如 `libAvaloniaNative.dylib`)会混用新旧版本,导致 macOS 上的 `StorageProvider` 文件/文件夹选择对话框在回调时 SIGSEGV 崩溃。
|
||||
- 相关 Avalonia issues: [#21102](https://github.com/AvaloniaUI/Avalonia/issues/21102), [#21150](https://github.com/AvaloniaUI/Avalonia/issues/21150), [#21313](https://github.com/AvaloniaUI/Avalonia/issues/21313),修复 PR: [#21104](https://github.com/AvaloniaUI/Avalonia/pull/21104)。
|
||||
- **Tests create temp files/directories** under `Path.GetTempPath()` and clean them up via `IDisposable`. Don't rely on a real audio directory for tests.
|
||||
- **`AudioMetadataReader` tests** use real WAV files from `data/source/` (not in git). When files are absent, tests silently return early (`if (wavFiles.Length == 0) return;`) — they pass without actually running assertions, not `Skip.If`. Running `dotnet test` will report 12 passes regardless of whether the fixture files exist.
|
||||
- **`.atl.txt` fixture files** are generated by `AtlFieldExtractor`. If the WAV files or the ATL library version change, re-run the extractor to regenerate them before running metadata tests.
|
||||
|
||||
## Data / Test Fixtures
|
||||
|
||||
- **`data/source/`** 包含有版权保护的 WAV 音频文件,**不会提交到 git**。按厂商分子目录:`Boom/`、`Wow Sound/`、`The Odessy/`、`Sound Idea/`。
|
||||
- 每个 WAV 旁有对应的 `.atl.txt` 文件(由 `AtlFieldExtractor` 生成),记录了 ATL 库解析出的完整元数据,供测试断言参考。
|
||||
- Boom 目录下的文件元数据最丰富(标准标签 + iXML 自定义字段 + BWF bext 字段 + 嵌入封面);Sound Idea 目录下的文件元数据最少(仅基础技术参数,无任何附加字段)。
|
||||
- **`AudioMetadataReader` 测试**使用 `data/source/Boom/` 和 `Sound Idea/` 中的 WAV 文件。文件不在 git 仓库中,测试通过 `if (wavFiles.Length == 0) return;` 静默返回(测试仍然 pass,但未执行实际断言)。
|
||||
- **路径解析**:测试运行时从 `bin/Debug/net10.0/` 启动,需向上回溯 6 级到达仓库根目录,再拼接 `data/source/`。
|
||||
- 需要一个UI设计。现在的界面属实有点丑陋了。
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
@@ -100,6 +100,43 @@ public class InverseBoolConverter : IValueConverter
|
||||
}
|
||||
}
|
||||
|
||||
public class HalfDoubleConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is double d && !double.IsNaN(d) && !double.IsInfinity(d))
|
||||
return d / 2.0;
|
||||
return 400.0;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public class MiddleTruncateConverter : IValueConverter
|
||||
{
|
||||
private const int KeepTail = 10;
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not string text || string.IsNullOrEmpty(text)) return value;
|
||||
|
||||
int maxLength = 60;
|
||||
if (parameter != null && int.TryParse(parameter.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsed))
|
||||
maxLength = parsed;
|
||||
|
||||
if (text.Length <= maxLength) return text;
|
||||
|
||||
int head = maxLength - KeepTail - 3;
|
||||
if (head <= 0) return text[..maxLength];
|
||||
|
||||
return string.Concat(text.AsSpan(0, head), "...", text.AsSpan(text.Length - KeepTail, KeepTail));
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public static class ValueConverters
|
||||
{
|
||||
public static readonly IValueConverter DurationFormat = new DurationFormatConverter();
|
||||
@@ -109,4 +146,6 @@ public static class ValueConverters
|
||||
public static readonly IValueConverter DurationToSliderMax = new DurationToSliderMaxConverter();
|
||||
public static readonly IValueConverter PositionFormat = new PositionFormatConverter();
|
||||
public static readonly IValueConverter InverseBool = new InverseBoolConverter();
|
||||
public static readonly IValueConverter HalfDouble = new HalfDoubleConverter();
|
||||
public static readonly IValueConverter MiddleTruncate = new MiddleTruncateConverter();
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<Border Grid.Row="0" Padding="6,4" BorderBrush="{DynamicResource SystemBaseLowColorBrush}"
|
||||
BorderThickness="0,0,0,1" Background="{DynamicResource SystemAltMediumColorBrush}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Button Content="📁 扫描目录" x:Name="ScanButton" Height="28" Padding="8,0"/>
|
||||
<Button Content="⚙ 偏好设置" Height="28" Padding="8,0" IsEnabled="False"/>
|
||||
<Button Content="扫描目录" x:Name="ScanButton" Height="28" Padding="8,0"/>
|
||||
<Button Content="偏好设置" Height="28" Padding="8,0" IsEnabled="False"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:GUI.ViewModels"
|
||||
xmlns:converters="using:GUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
@@ -10,8 +11,13 @@
|
||||
<vm:ScanProgressViewModel/>
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:HalfDoubleConverter x:Key="HalfDouble"/>
|
||||
<converters:MiddleTruncateConverter x:Key="MiddleTruncate"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border Padding="16" Background="{DynamicResource SystemBaseLowColorBrush}" CornerRadius="8"
|
||||
MaxWidth="400">
|
||||
Width="{Binding $parent[Window].Width, Converter={StaticResource HalfDouble}}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding StatusText}" FontWeight="SemiBold" FontSize="13" TextWrapping="Wrap"/>
|
||||
<ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress}" Height="8"/>
|
||||
@@ -27,8 +33,8 @@
|
||||
<TextBlock Text="{Binding SkippedFiles, StringFormat='跳过: {0}'}" FontSize="11"
|
||||
Foreground="{DynamicResource SystemBaseMediumColorBrush}"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding CurrentFile}" FontSize="11"
|
||||
TextTrimming="CharacterEllipsis" MaxWidth="368"/>
|
||||
<TextBlock Text="{Binding CurrentFile, Converter={StaticResource MiddleTruncate}, ConverterParameter=60}"
|
||||
FontSize="11"/>
|
||||
<Button Content="关闭" Command="{Binding DismissCommand}"
|
||||
IsVisible="{Binding HasError}" HorizontalAlignment="Right"/>
|
||||
</StackPanel>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFormat_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fe2d95a1d7b987bbb7785b2ad7e2e3e29075ba79cdf7619dd26c10d266c5_003FFormat_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGUI_002EViewModels_002EScanProgressViewModel_002Eg_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fbb74b3c6eaaeb5d783f31310eae5345eba75596a_003FGUI_002EViewModels_002EScanProgressViewModel_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGUI_002EViewModels_002EScanProgressViewModel_002EStartScan_002Eg_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003F1a6f588df342309187674fcc524512a9a4a46540_003FGUI_002EViewModels_002EScanProgressViewModel_002EStartScan_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGUI_002EViewModels_002EScanProgressViewModel_002EStartScan_002Eg_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fvar_003Ffolders_003F5b_003F4wt_005Fpwy11fz5nkdv7s47r3780000gn_003FT_003FSourceGeneratedDocuments_003F8E641B210524A46B7DCD4502_003FCommunityToolkit_002EMvvm_002ESourceGenerators_003FCommunityToolkit_002EMvvm_002ESourceGenerators_002ERelayCommandGenerator_003FGUI_002EViewModels_002EScanProgressViewModel_002EStartScan_002Eg_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGUI_002EViewModels_002EScanProgressViewModel_002EStartScan_002Eg_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003F1a6f588df342309187674fcc524512a9a4a46540_003FGUI_002EViewModels_002EScanProgressViewModel_002EStartScan_002Eg_002Ecs_002Fz_003A2_002D0/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIDbConnection_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01c4805efedf4381865d701be69b5ce5314910_003F77_003Ffb273e4d_003FIDbConnection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIRelayCommand_007BT_007D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003F49f2a292ecb417be1f61c426de242e503b4371a346bf5b68a843b462337e62_003FIRelayCommand_007BT_007D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOSPlatform_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003F9e988c941e6515999ebab7336243c87df16e340b8faeb57b5f562d2b8a7c2c_003FOSPlatform_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
||||
Reference in New Issue
Block a user