commit 311bd619124cc6975c90a11923e6b2fff9c4eee8 Author: ryzij Date: Fri Aug 28 04:03:32 2026 +0300 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2683417 --- /dev/null +++ b/.gitignore @@ -0,0 +1,214 @@ +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates +.vs/ + +# Build results + +*.sln.ide/ +[Dd]ebug/ +[Rr]elease/ +x64/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# NCrunch +_NCrunch_*/ +*.ncrunchsolution.user +nCrunchTemp_* + +# CodeRush +.cr/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings +Events_Avalonia.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + +################# +## Monodevelop +################# +*.userprefs +*.nugetreferenceswitcher + + +################# +## Rider +################# +.idea + +################# +## VS Code +################# +.vscode/ + +################# +## Cake +################# +tools/* +!tools/packages.config +.nuget +artifacts/ +nuget +Avalonia.XBuild.sln +project.lock.json +.idea/* + + +################## +## BenchmarkDotNet +################## +BenchmarkDotNet.Artifacts/ + +dirs.sln + + +################## +# Xcode +################## +Index/ +Logs/ +ModuleCache.noindex/ +Build/Intermediates.noindex/ +build-intermediate +obj-Direct2D1/ +obj-Skia/ + +################## +# Vim +################## +.vim +coc-settings.json +.ccls-cache +.ccls +*.map +src/Web/Avalonia.Web.Blazor/wwwroot/*.js +src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js \ No newline at end of file diff --git a/AMessanger/AMessanger.csproj b/AMessanger/AMessanger.csproj new file mode 100644 index 0000000..b8df6dd --- /dev/null +++ b/AMessanger/AMessanger.csproj @@ -0,0 +1,29 @@ + + + WinExe + net10.0 + enable + app.manifest + + + + + + + PreserveNewest + + + + + + + + + + None + All + + + + + diff --git a/AMessanger/App.axaml b/AMessanger/App.axaml new file mode 100644 index 0000000..3d9c53c --- /dev/null +++ b/AMessanger/App.axaml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/AMessanger/App.axaml.cs b/AMessanger/App.axaml.cs new file mode 100644 index 0000000..1ea586a --- /dev/null +++ b/AMessanger/App.axaml.cs @@ -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(), + }; + } + + base.OnFrameworkInitializationCompleted(); + } +} \ No newline at end of file diff --git a/AMessanger/Assets/avalonia-logo.ico b/AMessanger/Assets/avalonia-logo.ico new file mode 100644 index 0000000..f7da8bb Binary files /dev/null and b/AMessanger/Assets/avalonia-logo.ico differ diff --git a/AMessanger/ConnectionUrl b/AMessanger/ConnectionUrl new file mode 100644 index 0000000..1dc976f --- /dev/null +++ b/AMessanger/ConnectionUrl @@ -0,0 +1,2 @@ +# Замените на свой url +http://localhost:5004/hubs/chat \ No newline at end of file diff --git a/AMessanger/Program.cs b/AMessanger/Program.cs new file mode 100644 index 0000000..9076abb --- /dev/null +++ b/AMessanger/Program.cs @@ -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(); + + Services = services.BuildServiceProvider(); + + BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + } + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() +#if DEBUG + .WithDeveloperTools() +#endif + .WithInterFont() + .LogToTrace(); +} diff --git a/AMessanger/Services/ChatService.cs b/AMessanger/Services/ChatService.cs new file mode 100644 index 0000000..91b5f61 --- /dev/null +++ b/AMessanger/Services/ChatService.cs @@ -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? OnMessageRecieved; + + public bool IsConnected { get; private set; } = false; + + public ChatService(string url) + { + _connection = new HubConnectionBuilder() + .WithUrl(url) + .WithAutomaticReconnect() + .Build(); + + _connection.On("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); + } +} \ No newline at end of file diff --git a/AMessanger/ViewLocator.cs b/AMessanger/ViewLocator.cs new file mode 100644 index 0000000..3a5644d --- /dev/null +++ b/AMessanger/ViewLocator.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using AMessanger.ViewModels; + +namespace AMessanger; + +/// +/// Given a view model, returns the corresponding view if possible. +/// +[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; + } +} diff --git a/AMessanger/ViewModels/MainViewModel.cs b/AMessanger/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..1f698ce --- /dev/null +++ b/AMessanger/ViewModels/MainViewModel.cs @@ -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 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; + } +} diff --git a/AMessanger/ViewModels/ViewModelBase.cs b/AMessanger/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..f298ff9 --- /dev/null +++ b/AMessanger/ViewModels/ViewModelBase.cs @@ -0,0 +1,7 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace AMessanger.ViewModels; + +public abstract class ViewModelBase : ObservableObject +{ +} diff --git a/AMessanger/Views/MainWindow.axaml b/AMessanger/Views/MainWindow.axaml new file mode 100644 index 0000000..5570552 --- /dev/null +++ b/AMessanger/Views/MainWindow.axaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AMessanger/Views/MainWindow.axaml.cs b/AMessanger/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..c3e2aae --- /dev/null +++ b/AMessanger/Views/MainWindow.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace AMessanger.Views; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/AMessanger/app.manifest b/AMessanger/app.manifest new file mode 100644 index 0000000..32312e4 --- /dev/null +++ b/AMessanger/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/AMessangerClient.slnx b/AMessangerClient.slnx new file mode 100644 index 0000000..d60931c --- /dev/null +++ b/AMessangerClient.slnx @@ -0,0 +1,3 @@ + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3b3698 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# AMessanger Avalonia Client