First commit

This commit is contained in:
2026-08-28 04:03:32 +03:00
commit 311bd61912
16 changed files with 557 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
<None Update="ConnectionUrl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.1" />
<PackageReference Include="Avalonia.Desktop" Version="12.1.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.1" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.1.1" />
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.11" />
</ItemGroup>
</Project>
+15
View File
@@ -0,0 +1,15 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AMessanger.App"
xmlns:local="using:AMessanger"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.DataTemplates>
<local:ViewLocator />
</Application.DataTemplates>
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
+29
View File
@@ -0,0 +1,29 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using AMessanger.ViewModels;
using AMessanger.Views;
using Microsoft.Extensions.DependencyInjection;
namespace AMessanger;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = Program.Services.GetRequiredService<MainViewModel>(),
};
}
base.OnFrameworkInitializationCompleted();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+2
View File
@@ -0,0 +1,2 @@
# Замените на свой url
http://localhost:5004/hubs/chat
+54
View File
@@ -0,0 +1,54 @@
using Microsoft.Extensions.DependencyInjection;
using Avalonia;
using System;
using AMessanger.ViewModels;
using AMessanger.Services;
using System.IO;
using System.Linq;
namespace AMessanger;
sealed class Program
{
public static IServiceProvider Services { get; private set; } = null!;
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
{
var services = new ServiceCollection();
services.AddSingleton(p =>
{
var url = File
.ReadAllLines("ConnectionUrl")
.First(l => !string.IsNullOrWhiteSpace(l)
&& !l.TrimStart().StartsWith('#'));
var chat = new ChatService(url);
chat.ConnectAsync();
return chat;
});
// ViewModels
services.AddTransient<MainViewModel>();
Services = services.BuildServiceProvider();
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
#if DEBUG
.WithDeveloperTools()
#endif
.WithInterFont()
.LogToTrace();
}
+49
View File
@@ -0,0 +1,49 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
namespace AMessanger.Services;
public class ChatService
{
private readonly HubConnection _connection;
public event Action<string, string>? OnMessageRecieved;
public bool IsConnected { get; private set; } = false;
public ChatService(string url)
{
_connection = new HubConnectionBuilder()
.WithUrl(url)
.WithAutomaticReconnect()
.Build();
_connection.On<string, string>("ReceiveMessage", (user, message) =>
OnMessageRecieved?.Invoke(user, message));
}
public Task ConnectAsync(CancellationToken cancellationToken = default)
{
if (IsConnected)
return Task.CompletedTask;
IsConnected = true;
return _connection.StartAsync(cancellationToken);
}
public Task DisconnectAsync(CancellationToken cancellationToken = default)
{
if (!IsConnected)
return Task.CompletedTask;
IsConnected = false;
return _connection.StopAsync(cancellationToken);
}
public Task SendMessageAsync(string user, string message, CancellationToken cancellationToken = default)
{
return _connection.InvokeAsync("SendMessage", user, message, cancellationToken);
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using AMessanger.ViewModels;
namespace AMessanger;
/// <summary>
/// Given a view model, returns the corresponding view if possible.
/// </summary>
[RequiresUnreferencedCode(
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
public class ViewLocator : IDataTemplate
{
public Control? Build(object? param)
{
if (param is null)
return null;
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object? data)
{
return data is ViewModelBase;
}
}
+48
View File
@@ -0,0 +1,48 @@
using System.Collections.ObjectModel;
using AMessanger.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using Avalonia.Threading;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
using System.Diagnostics;
namespace AMessanger.ViewModels;
public partial class MainViewModel : ViewModelBase
{
[ObservableProperty]
public partial ObservableCollection<string> Messages { get; private set; } = new();
[ObservableProperty]
public partial string Message { get; set; } = string.Empty;
[ObservableProperty]
public partial string UserName { get; set; } = "Test user";
public ICommand SendButtonCommand { get; private set; }
private readonly ChatService _chat;
public MainViewModel(ChatService chatService)
{
_chat = chatService;
_chat.OnMessageRecieved += OnMessageRecived;
SendButtonCommand = new RelayCommand(SendMessage);
}
private void OnMessageRecived(string user, string message)
{
Dispatcher.UIThread.Post(() => Messages.Add($"{user}: {message}"));
Debug.WriteLine($"{user}: {message}");
}
private void SendMessage()
{
if (string.IsNullOrWhiteSpace(Message))
return;
_chat.SendMessageAsync(UserName, Message.Trim());
Message = string.Empty;
}
}
+7
View File
@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace AMessanger.ViewModels;
public abstract class ViewModelBase : ObservableObject
{
}
+40
View File
@@ -0,0 +1,40 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:AMessanger.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AMessanger.Views.MainWindow"
x:DataType="vm:MainViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="AMessanger">
<!-- <Design.DataContext> -->
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<!-- <vm:MainViewModel />
</Design.DataContext> -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
ItemsSource="{Binding Messages}" />
<Grid Grid.Row="1" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding UserName}" />
<TextBox Grid.Column="1" Text="{Binding Message}" />
<Button Grid.Column="2" Command="{Binding SendButtonCommand}">Send</Button>
</Grid>
</Grid>
</Window>
+11
View File
@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace AMessanger.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AMessanger.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>