localization + AudiRecorder

This commit is contained in:
Paul Schneider 2026-09-27 23:42:52 +01:00
commit 1e1b751906
Signed by: notazof
GPG key ID: AD4B9B545FBD7D12
11 changed files with 205 additions and 11 deletions

View file

@ -16,6 +16,7 @@
<PackageVersion Include="Avalonia.Headless" Version="12.1.2" /> <PackageVersion Include="Avalonia.Headless" Version="12.1.2" />
<PackageVersion Include="Avalonia.Headless.Xunit" Version="$(AvaloniaVersionBase)" /> <PackageVersion Include="Avalonia.Headless.Xunit" Version="$(AvaloniaVersionBase)" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersionBase)" /> <PackageVersion Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersionBase)" />
<PackageVersion Include="LibVLCSharp.Avalonia" Version="3.10.1" />
<PackageVersion Include="Mapsui" Version="5.1.0" /> <PackageVersion Include="Mapsui" Version="5.1.0" />
<PackageVersion Include="Mapsui.Avalonia12" Version="5.1.0" /> <PackageVersion Include="Mapsui.Avalonia12" Version="5.1.0" />
<PackageVersion Include="MarkdownViewer.Core" Version="1.0.5" /> <PackageVersion Include="MarkdownViewer.Core" Version="1.0.5" />
@ -23,6 +24,8 @@
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" /> <PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" /> <PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageVersion Include="Mono.Options" Version="6.12.0.148" /> <PackageVersion Include="Mono.Options" Version="6.12.0.148" />
<PackageVersion Include="PvRecorder" Version="1.2.13" />
<PackageVersion Include="Silk.NET.OpenAL" Version="2.23.0" />
<PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.10.0.1" /> <PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.10.0.1" />
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.2.0.3" /> <PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.2.0.3" />
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.Runtime" Version="2.11.0.1" /> <PackageVersion Include="Xamarin.AndroidX.Lifecycle.Runtime" Version="2.11.0.1" />

View file

@ -11,6 +11,7 @@ using PostIt.Services;
using PostIt.ViewModels; using PostIt.ViewModels;
using PostIt.Views; using PostIt.Views;
using PostIt.Helpers; using PostIt.Helpers;
using System.Globalization;
namespace PostIt; namespace PostIt;
@ -36,6 +37,7 @@ public partial class App : Application
public override void Initialize() public override void Initialize()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
@ -65,7 +67,7 @@ public partial class App : Application
{ {
singleViewPlatform.MainView = View = ServiceProvider!.GetRequiredService<MainView>(); singleViewPlatform.MainView = View = ServiceProvider!.GetRequiredService<MainView>();
ConfigureRootView(View); ConfigureRootView(View);
ApplyDarkMode(settings); ApplyStettings(settings);
} }
public void InitializeActivityLifetime(Settings settings, IActivityApplicationLifetime singleViewFactoryApplicationLifetime) public void InitializeActivityLifetime(Settings settings, IActivityApplicationLifetime singleViewFactoryApplicationLifetime)
@ -74,7 +76,7 @@ public partial class App : Application
() => () =>
{ {
this.ConfigureRootView(ServiceProvider!.GetRequiredService<MainView>()); this.ConfigureRootView(ServiceProvider!.GetRequiredService<MainView>());
ApplyDarkMode(settings); ApplyStettings(settings);
return View!; return View!;
}; };
} }
@ -85,7 +87,7 @@ public partial class App : Application
desktop.MainWindow = window; desktop.MainWindow = window;
this.ConfigureRootView(window.MainView); this.ConfigureRootView(window.MainView);
ApplyDarkMode(settings); ApplyStettings(settings);
} }
public void ConfigureRootView(MainView rootView) public void ConfigureRootView(MainView rootView)
@ -126,9 +128,11 @@ public partial class App : Application
View = mainView ?? throw new ArgumentNullException(nameof(mainView)); View = mainView ?? throw new ArgumentNullException(nameof(mainView));
} }
private static void ApplyDarkMode(Settings settings) private static void ApplyStettings(Settings settings)
{ {
Application.Current!.RequestedThemeVariant = if (!string.IsNullOrWhiteSpace(settings.SelectedLanguage))
Yavsc.Lang.Resources.Culture = new CultureInfo(settings.SelectedLanguage);
Current!.RequestedThemeVariant =
settings.DarkMode ? ThemeVariant.Dark : ThemeVariant.Light; settings.DarkMode ? ThemeVariant.Dark : ThemeVariant.Light;
} }

View file

@ -0,0 +1,6 @@
using System;
public interface IPCMProcessor : IDisposable
{
void Process(short[] audioFrame);
}

View file

@ -0,0 +1,72 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Yavsc.Lang {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Yavsc.Lang.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Bienvenue dans PostIt!.
/// </summary>
public static string GreetingText {
get {
return ResourceManager.GetString("GreetingText", resourceCulture);
}
}
}
}

View file

@ -0,0 +1,5 @@
<root>
<data name="GreetingText">
<value>Welcome to PostIt!</value>
</data>
</root>

View file

@ -0,0 +1,5 @@
<root>
<data name="GreetingText">
<value>Bemvindo em PostIt!</value>
</data>
</root>

View file

@ -0,0 +1,5 @@
<root>
<data name="GreetingText">
<value>Bienvenue dans PostIt!</value>
</data>
</root>

View file

@ -7,6 +7,8 @@
<FileVersion>1.1.0.0</FileVersion> <FileVersion>1.1.0.0</FileVersion>
<InformationalVersion>1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e</InformationalVersion> <InformationalVersion>1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e</InformationalVersion>
<Version>1.1.0-estimate.1</Version> <Version>1.1.0-estimate.1</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**" /> <AvaloniaResource Include="Assets\**" />
@ -30,6 +32,7 @@
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets> <IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets> <PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="LibVLCSharp.Avalonia" />
<PackageReference Include="Mapsui" /> <PackageReference Include="Mapsui" />
<PackageReference Include="Mapsui.Avalonia12" /> <PackageReference Include="Mapsui.Avalonia12" />
<PackageReference Include="CommunityToolkit.Mvvm" /> <PackageReference Include="CommunityToolkit.Mvvm" />
@ -37,9 +40,25 @@
<PackageReference Include="MarkdownViewer.Core" /> <PackageReference Include="MarkdownViewer.Core" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Mono.Options" /> <PackageReference Include="Mono.Options" />
<PackageReference Include="PvRecorder" />
<PackageReference Include="Silk.NET.OpenAL" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" /> <ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
<ProjectReference Include="../../Yavsc.Api.Client/Yavsc.Api.Client.csproj" /> <ProjectReference Include="../../Yavsc.Api.Client/Yavsc.Api.Client.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Lang/Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Lang/Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Lang/Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project> </Project>

View file

@ -0,0 +1,67 @@
using System;
using System.Threading;
using Pv;
public class AudioRecorder : IDisposable
{
private int frameLength;
PvRecorder recorder;
Thread thread;
short[] audioFrame;
IPCMProcessor processor;
public bool processing { get; private set; }
public AudioRecorder(IPCMProcessor processor, int frameLength=512, int deviceIndex = -1)
{
this.frameLength = frameLength;
this.recorder = PvRecorder.Create(frameLength: frameLength, deviceIndex: deviceIndex);
this.audioFrame = new short[frameLength];
this.processor=processor;
}
public static string [] GetAvailableDevices()
{
return PvRecorder.GetAvailableDevices();
}
public void Start()
{
recorder.Start();
thread = new Thread(ThreadLoop);
thread.Start();
}
public void ThreadLoop()
{
while (recorder.IsRecording)
{
if (thread.ThreadState != ThreadState.Running)
{
break;
}
if (processing)
{
Thread.Sleep(10);
continue;
}
processing = true;
audioFrame = recorder.Read();
processor.Process(audioFrame);
processing = false;
}
}
public void Stop()
{
thread.Interrupt();
thread.Join();
recorder.Stop();
thread = null;
}
public void Dispose()
{
recorder.Dispose();
}
}

View file

@ -8,6 +8,7 @@ using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Globalization;
[assembly: InternalsVisibleTo("PostIt.Tests")] [assembly: InternalsVisibleTo("PostIt.Tests")]
@ -44,6 +45,7 @@ public partial class Settings : ViewModelBase
public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret.");
[JsonIgnore]
public bool Loaded { get; private set; } = false; public bool Loaded { get; private set; } = false;
@ -59,8 +61,16 @@ public partial class Settings : ViewModelBase
/// <see cref="Authentication"/>. /// <see cref="Authentication"/>.
/// </summary> /// </summary>
[ObservableProperty] [ObservableProperty]
[JsonIgnore]
public partial bool IsDirty { get; private set; } = false; public partial bool IsDirty { get; private set; } = false;
[JsonIgnore]
public override bool CanNavigateNext { get => false; protected set => throw new System.NotImplementedException(); }
[JsonIgnore]
public override bool CanNavigatePrevious { get => true; protected set => throw new System.NotImplementedException(); }
public string SelectedLanguage { get; internal set; } = CultureInfo.CurrentUICulture.Name;
/// <summary> /// <summary>
/// Guards every mutation of the observable state. <c>[ObservableProperty]</c> /// Guards every mutation of the observable state. <c>[ObservableProperty]</c>
@ -532,7 +542,4 @@ public partial class Settings : ViewModelBase
/// </summary> /// </summary>
partial void OnIsDirtyChanged(bool value) => SaveCommand.NotifyCanExecuteChanged(); partial void OnIsDirtyChanged(bool value) => SaveCommand.NotifyCanExecuteChanged();
public override bool CanNavigateNext { get => false; protected set => throw new System.NotImplementedException(); }
public override bool CanNavigatePrevious { get => true; protected set => throw new System.NotImplementedException(); }
} }

View file

@ -1,6 +1,7 @@
<ContentPage xmlns="https://github.com/avaloniaui" <ContentPage 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:PostIt.ViewModels" xmlns:vm="using:PostIt.ViewModels"
xmlns:lang="using:Yavsc.Lang"
x:Class="PostIt.Views.HomePage" x:Class="PostIt.Views.HomePage"
x:DataType="vm:HomePageViewModel" x:DataType="vm:HomePageViewModel"
Header="Home"> Header="Home">
@ -10,7 +11,7 @@
<StackPanel HorizontalAlignment="Center" <StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Spacing="12"> Spacing="12">
<TextBlock Text="Welcome to the Home Page" <TextBlock Text="{x:Static lang:Resources.GreetingText}"
FontSize="22" FontSize="22"
FontWeight="SemiBold" FontWeight="SemiBold"
HorizontalAlignment="Center"/> HorizontalAlignment="Center"/>