Creat GUI project framework.

This commit is contained in:
2026-05-26 13:57:11 +08:00
parent 86ee000af9
commit 0f73dfdb84
5 changed files with 104 additions and 8 deletions
+13 -7
View File
@@ -3,23 +3,29 @@
## Build & Test ## Build & Test
```bash ```bash
dotnet build # Build all projects dotnet build # Build all projects
dotnet test # Run all tests dotnet test # Run all tests
dotnet test --filter "FullyQualifiedName~ClassName.MethodName" # Run single test dotnet test --filter "FullyQualifiedName~ClassName.MethodName" # Run single test
dotnet run --project src/GUI # Launch the Avalonia desktop app
``` ```
## Architecture ## Architecture
- **3-project solution** (`src/Resonance.sln`): - **4-project solution** (`src/Resonance.sln`):
- `Core` — class library: audio file scanning, metadata reading (ATL), SQLite persistence (Dapper) - `Core` — class library: audio file scanning, metadata reading (ATL), SQLite persistence (Dapper)
- `Core.Tests` — xUnit + FluentAssertions + Moq - `Core.Tests` — xUnit + FluentAssertions + Moq
- `AtlFieldExtractor` — CLI that dumps ATL metadata fields from WAV files to `.atl.txt` - `AtlFieldExtractor` — CLI that dumps ATL metadata fields from WAV files to `.atl.txt` to test ATL library
- **No Avalonia UI yet** — README mentions it but only the Core library exists. - `GUI` — Avalonia 12 desktop app: audio asset management UI
- **MVVM architecture** (GUI project):
- Based on **CommunityToolkit.Mvvm** v8.4.1 — source-generator-driven MVVM toolkit.
- Base class: `ViewModelBase` (abstract, extends `ObservableObject`). Use `[ObservableProperty]` for bindable properties, `[RelayCommand]` for commands, and `SetProperty<T>()` for manual property change notification.
- View-ViewModel binding: `ViewLocator` implements `IDataTemplate` — convention-based resolution by replacing `"ViewModel"` with `"View"` in the type name (e.g., `MainWindowViewModel` → looks for `MainWindow` in `GUI.Views`). Uses `Activator.CreateInstance()`; **no DI container** is configured.
- Compiled bindings are enabled by default (`AvaloniaUseCompiledBindingsByDefault=true` in `.csproj`). Annotate XAML with `x:DataType="vm:MyViewModel"` for compile-time validation.
- `App.axaml` applies `FluentTheme` and registers `ViewLocator` globally. `App.axaml.cs` manually instantiates `MainWindow``MainWindowViewModel` in `OnFrameworkInitializationCompleted`.
- Target framework: `net10.0` (requires .NET 10 SDK — currently `10.0.103`). - Target framework: `net10.0` (requires .NET 10 SDK — currently `10.0.103`).
## Known Issues / Gotchas ## Known Issues / Gotchas
- **Table name mismatch**: `Database.InitializeDatabase()` creates table `sounds`, but `AddEntry()` / `AddEntries()` / `EntryExists()` reference `audio_files`. Any call to insert or query will fail at runtime. `Database` currently has no test coverage.
- **Tests create temp files/directories** under `Path.GetTempPath()` and clean them up via `IDisposable`. Don't rely on a real audio directory for tests. - **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. - **`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. - **`.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.
@@ -35,7 +41,7 @@ dotnet test --filter "FullyQualifiedName~ClassName.MethodName" # Run single tes
## Conventions ## Conventions
- Code and comments are in **Chinese**. - Code and comments are in **Chinese**.
- Namespace: `OCES.Resonance.Core` (Core), `OCES.Resonance.AtlFieldExtractor` (extractor), `OCES.Resonance.Core.Tests` (tests). - Namespace: `OCES.Resonance.Core` (Core), `OCES.Resonance.AtlFieldExtractor` (extractor), `Core.Tests` (tests), `GUI` / `GUI.Views` / `GUI.ViewModels` (GUI app).
## AI 助手工具说明 ## AI 助手工具说明
+48
View File
@@ -0,0 +1,48 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GUI {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Locale {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Locale() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("GUI.Locale", typeof(Locale).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+15
View File
@@ -28,4 +28,19 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" /> <ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Assets\Locale.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Locale.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Assets\Locale.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Locale.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project> </Project>
+7 -1
View File
@@ -23,4 +23,10 @@
&lt;TestId&gt;xUnit::24F92458-FB39-44BE-A32F-41275183AF1B::net10.0::Core.Tests.IntegratedTest.ScanReadWritePipeline_ShouldGenerateDatabase&lt;/TestId&gt; &lt;TestId&gt;xUnit::24F92458-FB39-44BE-A32F-41275183AF1B::net10.0::Core.Tests.IntegratedTest.ScanReadWritePipeline_ShouldGenerateDatabase&lt;/TestId&gt;
&lt;TestId&gt;xUnit::24F92458-FB39-44BE-A32F-41275183AF1B::net10.0::Core.Tests.IntegratedTest&lt;/TestId&gt; &lt;TestId&gt;xUnit::24F92458-FB39-44BE-A32F-41275183AF1B::net10.0::Core.Tests.IntegratedTest&lt;/TestId&gt;
&lt;/TestAncestor&gt; &lt;/TestAncestor&gt;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary> &lt;/SessionState&gt;</s:String>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=GUI_002FAssets_002FLocale/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=GUI_002FLocale/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=GUI_002FLocale/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=GUI_002FResources1/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=GUI_002FResources1/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>