From 019fb3d462e77dd64d1a9581110a1c8d7a1b83ad Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:08:31 +0200 Subject: [PATCH] Code cleanup --- src/Decho/Services/ConnectionService.cs | 258 +++++++++--------- .../ViewModels/AutocompleteController.cs | 17 +- src/Decho/ViewModels/AutocompleteProvider.cs | 25 +- src/Decho/ViewModels/ChannelViewModel.cs | 6 +- src/Decho/ViewModels/ChatViewModel.cs | 14 +- src/Decho/ViewModels/MainWindowViewModel.cs | 3 +- .../ViewModels/MessageComposerViewModel.cs | 3 +- src/Decho/ViewModels/UserViewModel.cs | 27 +- src/Decho/Views/ConnectDialogWindow.axaml.cs | 2 +- src/Decho/Views/CreateChannelWindow.axaml.cs | 2 +- src/Decho/Views/MessageItemView.axaml.cs | 3 +- src/Decho/Views/OnlineUsersView.axaml.cs | 2 +- src/Decho/Views/ProfileWindow.axaml.cs | 12 +- src/Decho/Views/ViewExtensions.cs | 2 +- 14 files changed, 171 insertions(+), 205 deletions(-) diff --git a/src/Decho/Services/ConnectionService.cs b/src/Decho/Services/ConnectionService.cs index 7f8331b..18cb442 100644 --- a/src/Decho/Services/ConnectionService.cs +++ b/src/Decho/Services/ConnectionService.cs @@ -35,86 +35,6 @@ public sealed class ConnectionService : IDisposable private readonly Dictionary _connections = new(StringComparer.OrdinalIgnoreCase); internal IReadOnlyDictionary Connections => _connections; - private async Task ConnectCoreAsync(ConnectDialogResult dialogResult) - { - ConnectionManager conn = new ConnectionManager(); - - ConnectResult result; - try - { - result = await conn.ConnectAsync(dialogResult, _ => { }); - } - catch - { - await conn.DisposeAsync(); - throw; - } - - LoginResponse login = result.Login; - UserModel userModel = new UserModel(login.Username, login.DisplayName ?? login.Username, login.NicknameColor); - - ObservableCollection channels = []; - ServerModel serverModel = new ServerModel( - Guid.NewGuid().ToString("N"), - new Uri(dialogResult.ServerUrl).Host, - channels, - dialogResult.ServerUrl, - isConnected: true, - connectedUser: login.Username); - - ServerConnection serverEntry = new ServerConnection(conn, conn.Api!, serverModel, userModel); - - foreach (ChannelDto ch in result.Channels) - { - ChannelModel channelModel = ChannelModelFromDto(ch); - channels.Add(channelModel); - } - - WireConnectionEvents(serverEntry, conn); - - _connections[dialogResult.ServerUrl] = serverEntry; - SaveRefreshToken(dialogResult.ServerUrl, dialogResult.RememberMe); - ServerAdded?.Invoke(serverModel); - - AutoJoinRemainingChannels(dialogResult.ServerUrl, result.Channels); - - return serverModel; - } - - private async void AutoJoinRemainingChannels(string serverUrl, List channels) - { - ClientConfig config = ConfigManager.Load(); - List leftChannels = config.SavedServers - .FirstOrDefault(s => string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase)) - ?.LeftChannels ?? []; - - foreach (ChannelDto ch in channels) - { - if (string.Equals(ch.Name, HubConstants.DefaultChannel, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - if (leftChannels.Contains(ch.Name, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - - try - { - _ = await JoinChannelAsync(serverUrl, ch.Name); - } - catch (EchoHub.Client.Services.ChannelPasswordRequiredException) - { - // protected channel — join stays manual - } - catch - { - // skip channels we can't join - } - } - } - public async Task ConnectAsync(string serverUrl, string username, string password, bool isRegister, bool rememberMe) { ConnectDialogResult dialogResult = new ConnectDialogResult( @@ -129,24 +49,6 @@ public sealed class ConnectionService : IDisposable _ = await ConnectCoreAsync(dialogResult); } - private async Task CleanupConnectionAsync(string serverUrl) - { - if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry)) - { - return null; - } - - entry.Server.IsConnected = false; - entry.Server.IsConnecting = false; - - await entry.Manager.CleanupAsync(); - entry.ApiClient.Dispose(); - await entry.Manager.DisposeAsync(); - - _ = _connections.Remove(serverUrl); - return entry; - } - public async Task DisconnectAsync(string serverUrl) { ServerConnection? entry = await CleanupConnectionAsync(serverUrl); @@ -163,26 +65,6 @@ public sealed class ConnectionService : IDisposable ServerRemoved?.Invoke(serverUrl); } - private static void ModifyConfig(string serverUrl, Action action) - { - ClientConfig config = ConfigManager.Load(); - SavedServer? saved = config.SavedServers.FirstOrDefault(s => - string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase)); - action(config, saved); - ConfigManager.Save(config); - } - - private static void RemoveServerFromConfig(string serverUrl) - { - ModifyConfig(serverUrl, (config, saved) => - { - if (saved is not null) - { - _ = config.SavedServers.Remove(saved); - } - }); - } - public async Task SendMessageAsync(string serverUrl, string channelName, string content) { if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry)) @@ -256,17 +138,6 @@ public sealed class ConnectionService : IDisposable } } - private static void RemoveFromLeftChannels(string serverUrl, string channelName) - { - ClientConfig config = ConfigManager.Load(); - SavedServer? saved = config.SavedServers - .FirstOrDefault(s => string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase)); - if (saved is not null && saved.LeftChannels.Remove(channelName)) - { - ConfigManager.Save(config); - } - } - public async Task> GetHistoryAsync(string serverUrl, string channelName, int count = HubConstants.DefaultHistoryCount, int offset = 0) { if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry)) @@ -591,6 +462,135 @@ public sealed class ConnectionService : IDisposable return _connections.TryGetValue(serverUrl, out ServerConnection? conn) ? conn : null; } + private static void ModifyConfig(string serverUrl, Action action) + { + ClientConfig config = ConfigManager.Load(); + SavedServer? saved = config.SavedServers.FirstOrDefault(s => + string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase)); + action(config, saved); + ConfigManager.Save(config); + } + + private static void RemoveServerFromConfig(string serverUrl) + { + ModifyConfig(serverUrl, (config, saved) => + { + if (saved is not null) + { + _ = config.SavedServers.Remove(saved); + } + }); + } + + private static void RemoveFromLeftChannels(string serverUrl, string channelName) + { + ClientConfig config = ConfigManager.Load(); + SavedServer? saved = config.SavedServers + .FirstOrDefault(s => string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase)); + if (saved is not null && saved.LeftChannels.Remove(channelName)) + { + ConfigManager.Save(config); + } + } + + private async Task ConnectCoreAsync(ConnectDialogResult dialogResult) + { + ConnectionManager conn = new ConnectionManager(); + + ConnectResult result; + try + { + result = await conn.ConnectAsync(dialogResult, _ => { }); + } + catch + { + await conn.DisposeAsync(); + throw; + } + + LoginResponse login = result.Login; + UserModel userModel = new UserModel(login.Username, login.DisplayName ?? login.Username, login.NicknameColor); + + ObservableCollection channels = []; + ServerModel serverModel = new ServerModel( + Guid.NewGuid().ToString("N"), + new Uri(dialogResult.ServerUrl).Host, + channels, + dialogResult.ServerUrl, + isConnected: true, + connectedUser: login.Username); + + ServerConnection serverEntry = new ServerConnection(conn, conn.Api!, serverModel, userModel); + + foreach (ChannelDto ch in result.Channels) + { + ChannelModel channelModel = ChannelModelFromDto(ch); + channels.Add(channelModel); + } + + WireConnectionEvents(serverEntry, conn); + + _connections[dialogResult.ServerUrl] = serverEntry; + SaveRefreshToken(dialogResult.ServerUrl, dialogResult.RememberMe); + ServerAdded?.Invoke(serverModel); + + AutoJoinRemainingChannels(dialogResult.ServerUrl, result.Channels); + + return serverModel; + } + + private async void AutoJoinRemainingChannels(string serverUrl, List channels) + { + ClientConfig config = ConfigManager.Load(); + List leftChannels = config.SavedServers + .FirstOrDefault(s => string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase)) + ?.LeftChannels ?? []; + + foreach (ChannelDto ch in channels) + { + if (string.Equals(ch.Name, HubConstants.DefaultChannel, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (leftChannels.Contains(ch.Name, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + try + { + _ = await JoinChannelAsync(serverUrl, ch.Name); + } + catch (EchoHub.Client.Services.ChannelPasswordRequiredException) + { + // protected channel — join stays manual + } + catch + { + // skip channels we can't join + } + } + } + + private async Task CleanupConnectionAsync(string serverUrl) + { + if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry)) + { + return null; + } + + entry.Server.IsConnected = false; + entry.Server.IsConnecting = false; + + await entry.Manager.CleanupAsync(); + entry.ApiClient.Dispose(); + await entry.Manager.DisposeAsync(); + + _ = _connections.Remove(serverUrl); + return entry; + } + private void SaveRefreshToken(string serverUrl, bool rememberMe) { if (!rememberMe) diff --git a/src/Decho/ViewModels/AutocompleteController.cs b/src/Decho/ViewModels/AutocompleteController.cs index 8db20e6..04b1117 100644 --- a/src/Decho/ViewModels/AutocompleteController.cs +++ b/src/Decho/ViewModels/AutocompleteController.cs @@ -3,29 +3,27 @@ using System.Text.RegularExpressions; namespace Decho.ViewModels; -public sealed class AutocompleteController : ViewModelBase +public sealed class AutocompleteController(IEnumerable providers) : ViewModelBase { - private readonly List _providers; - private static readonly Regex TriggerPattern = new(@"([@#])(\w*)$", RegexOptions.Compiled); - + private readonly List _providers = providers.ToList(); public ObservableCollection FilteredItems { get; } = []; public bool ShowPopup { - get => field; + get; set => this.RaiseAndSetIfChanged(ref field, value); } public int SelectedIndex { - get => field; + get; set => this.RaiseAndSetIfChanged(ref field, value); } public string FilterText { - get => field; + get; set => this.RaiseAndSetIfChanged(ref field, value); } @@ -33,11 +31,6 @@ public sealed class AutocompleteController : ViewModelBase public int TriggerCharIndex { get; private set; } - public AutocompleteController(IEnumerable providers) - { - _providers = providers.ToList(); - } - public void Update(string text) { if (string.IsNullOrEmpty(text) || _providers.Count == 0) diff --git a/src/Decho/ViewModels/AutocompleteProvider.cs b/src/Decho/ViewModels/AutocompleteProvider.cs index a1d8681..11c1d1a 100644 --- a/src/Decho/ViewModels/AutocompleteProvider.cs +++ b/src/Decho/ViewModels/AutocompleteProvider.cs @@ -1,23 +1,10 @@ namespace Decho.ViewModels; -public sealed class AutocompleteProvider +public sealed class AutocompleteProvider(char trigger, Func> itemsSource, string insertPrefix, int maxResults = 10) { - public char Trigger { get; } - public Func> ItemsSource { get; } - public Func Filter { get; } - public Func FormatInsertion { get; } - public int MaxResults { get; } - - public AutocompleteProvider( - char trigger, - Func> itemsSource, - string insertPrefix, - int maxResults = 10) - { - Trigger = trigger; - ItemsSource = itemsSource; - Filter = (item, filter) => item.StartsWith(filter, StringComparison.OrdinalIgnoreCase); - FormatInsertion = item => $"{insertPrefix}{item} "; - MaxResults = maxResults; - } + public char Trigger { get; } = trigger; + public Func> ItemsSource { get; } = itemsSource; + public Func Filter { get; } = (item, filter) => item.StartsWith(filter, StringComparison.OrdinalIgnoreCase); + public Func FormatInsertion { get; } = item => $"{insertPrefix}{item} "; + public int MaxResults { get; } = maxResults; } \ No newline at end of file diff --git a/src/Decho/ViewModels/ChannelViewModel.cs b/src/Decho/ViewModels/ChannelViewModel.cs index d7b69b2..df74915 100644 --- a/src/Decho/ViewModels/ChannelViewModel.cs +++ b/src/Decho/ViewModels/ChannelViewModel.cs @@ -34,13 +34,13 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase public bool IsLocked { - get => field; + get; set => this.RaiseAndSetIfChanged(ref field, value); } public int UnreadCount { - get => field; + get; set { if (field == value) @@ -60,7 +60,7 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase public int MentionCount { - get => field; + get; set { if (field == value) diff --git a/src/Decho/ViewModels/ChatViewModel.cs b/src/Decho/ViewModels/ChatViewModel.cs index 3a2869f..0c30102 100644 --- a/src/Decho/ViewModels/ChatViewModel.cs +++ b/src/Decho/ViewModels/ChatViewModel.cs @@ -39,7 +39,7 @@ public sealed class ChatViewModel : ViewModelBase public bool ShowOnlineUsers { - get => field; + get; set => this.RaiseAndSetIfChanged(ref field, value); } @@ -47,7 +47,7 @@ public sealed class ChatViewModel : ViewModelBase public string OnlineUserCount { - get => field; + get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty; @@ -63,11 +63,6 @@ public sealed class ChatViewModel : ViewModelBase ToggleUsersPanelCommand = ReactiveCommand.Create(ToggleUsersPanel); } - private void ToggleUsersPanel() - { - ShowOnlineUsers = !ShowOnlineUsers; - } - public void SetChannel(ChannelViewModel? channel, string serverUrl = "", bool isServerConnected = true) { if (channel is null) @@ -145,4 +140,9 @@ public sealed class ChatViewModel : ViewModelBase OnlineUsers.Clear(); OnlineUserCount = string.Empty; } + + private void ToggleUsersPanel() + { + ShowOnlineUsers = !ShowOnlineUsers; + } } \ No newline at end of file diff --git a/src/Decho/ViewModels/MainWindowViewModel.cs b/src/Decho/ViewModels/MainWindowViewModel.cs index 1bcb437..556f015 100644 --- a/src/Decho/ViewModels/MainWindowViewModel.cs +++ b/src/Decho/ViewModels/MainWindowViewModel.cs @@ -1059,5 +1059,4 @@ public sealed class MainWindowViewModel : ViewModelBase { return Chat.CurrentServerUrl; } -} - +} \ No newline at end of file diff --git a/src/Decho/ViewModels/MessageComposerViewModel.cs b/src/Decho/ViewModels/MessageComposerViewModel.cs index cd54a43..e8762cf 100644 --- a/src/Decho/ViewModels/MessageComposerViewModel.cs +++ b/src/Decho/ViewModels/MessageComposerViewModel.cs @@ -13,10 +13,9 @@ public sealed class MessageComposerViewModel : ViewModelBase public event Action? FileUploadRequested; - private CommandHandler? _commandHandler; - private readonly ObservableCollection _onlineUsers = []; private readonly ObservableCollection _channelNames = []; + private CommandHandler? _commandHandler; public string Draft { diff --git a/src/Decho/ViewModels/UserViewModel.cs b/src/Decho/ViewModels/UserViewModel.cs index 21f5eff..be1d43d 100644 --- a/src/Decho/ViewModels/UserViewModel.cs +++ b/src/Decho/ViewModels/UserViewModel.cs @@ -5,13 +5,14 @@ using EchoHub.Core.Models; namespace Decho.ViewModels; -public sealed class UserViewModel : ViewModelBase +public sealed class UserViewModel(UserPresenceDto dto) : ViewModelBase { - public string Username { get; } - public string DisplayName { get; } - public string? NicknameColor { get; } - public string? StatusMessage { get; } - public ServerRole Role { get; } + private UserStatus _status = dto.Status; + public string Username { get; } = dto.Username; + public string DisplayName { get; } = dto.DisplayName ?? dto.Username; + public string? NicknameColor { get; } = dto.NicknameColor; + public string? StatusMessage { get; } = dto.StatusMessage; + public ServerRole Role { get; } = dto.Role; public UserStatus Status { @@ -30,8 +31,6 @@ public sealed class UserViewModel : ViewModelBase } } - private UserStatus _status; - public string FullName => DisplayName ?? Username; public IBrush? DisplayColor @@ -71,14 +70,4 @@ public sealed class UserViewModel : ViewModelBase UserStatus.Invisible => "Invisible", _ => "Offline", }; - - public UserViewModel(UserPresenceDto dto) - { - Username = dto.Username; - DisplayName = dto.DisplayName ?? dto.Username; - NicknameColor = dto.NicknameColor; - StatusMessage = dto.StatusMessage; - Role = dto.Role; - _status = dto.Status; - } -} +} \ No newline at end of file diff --git a/src/Decho/Views/ConnectDialogWindow.axaml.cs b/src/Decho/Views/ConnectDialogWindow.axaml.cs index 66d832a..c1973c9 100644 --- a/src/Decho/Views/ConnectDialogWindow.axaml.cs +++ b/src/Decho/Views/ConnectDialogWindow.axaml.cs @@ -147,4 +147,4 @@ public sealed class ConnectDialogResult public bool RememberMe { get; set; } public bool IsSavedSession { get; set; } public string? SavedRefreshToken { get; set; } -} +} \ No newline at end of file diff --git a/src/Decho/Views/CreateChannelWindow.axaml.cs b/src/Decho/Views/CreateChannelWindow.axaml.cs index ec83642..1a7f691 100644 --- a/src/Decho/Views/CreateChannelWindow.axaml.cs +++ b/src/Decho/Views/CreateChannelWindow.axaml.cs @@ -29,4 +29,4 @@ public partial class CreateChannelWindow : Window ResultPassword = string.IsNullOrWhiteSpace(ChannelPassword.Text) ? null : ChannelPassword.Text; Close(true); } -} +} \ No newline at end of file diff --git a/src/Decho/Views/MessageItemView.axaml.cs b/src/Decho/Views/MessageItemView.axaml.cs index 9c6e18a..aa239ae 100644 --- a/src/Decho/Views/MessageItemView.axaml.cs +++ b/src/Decho/Views/MessageItemView.axaml.cs @@ -16,6 +16,7 @@ namespace Decho.Views; public partial class MessageItemView : UserControl { + private static readonly Regex MentionRegex = new(@"@(\w+)", RegexOptions.Compiled); private CancellationTokenSource? _loadCts; private string? _loadedMessageId; @@ -25,8 +26,6 @@ public partial class MessageItemView : UserControl DataContextChanged += OnDataContextChanged; } - private static readonly Regex MentionRegex = new(@"@(\w+)", RegexOptions.Compiled); - private void OnDataContextChanged(object? sender, EventArgs args) { if (DataContext is MessageViewModel newMsg && newMsg.Model.Id == _loadedMessageId) diff --git a/src/Decho/Views/OnlineUsersView.axaml.cs b/src/Decho/Views/OnlineUsersView.axaml.cs index 5a14053..252d2ab 100644 --- a/src/Decho/Views/OnlineUsersView.axaml.cs +++ b/src/Decho/Views/OnlineUsersView.axaml.cs @@ -40,4 +40,4 @@ public partial class OnlineUsersView : UserControl ProfileWindow dialog = new ProfileWindow(profile); await dialog.ShowDialog(parent); } -} +} \ No newline at end of file diff --git a/src/Decho/Views/ProfileWindow.axaml.cs b/src/Decho/Views/ProfileWindow.axaml.cs index c362074..7cd75cd 100644 --- a/src/Decho/Views/ProfileWindow.axaml.cs +++ b/src/Decho/Views/ProfileWindow.axaml.cs @@ -51,11 +51,6 @@ public sealed partial class ProfileWindow : Window LastSeenText.Text = profile.LastSeenAt.ToString("g"); } - private void OnCloseClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) - { - Close(); - } - private static string FormatStatus(UserStatus status) { return status switch @@ -90,4 +85,9 @@ public sealed partial class ProfileWindow : Window _ => role.ToString(), }; } -} + + private void OnCloseClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + Close(); + } +} \ No newline at end of file diff --git a/src/Decho/Views/ViewExtensions.cs b/src/Decho/Views/ViewExtensions.cs index 58d4776..08f8120 100644 --- a/src/Decho/Views/ViewExtensions.cs +++ b/src/Decho/Views/ViewExtensions.cs @@ -15,4 +15,4 @@ internal static class ViewExtensions { return TopLevel.GetTopLevel(control)?.DataContext as MainWindowViewModel; } -} +} \ No newline at end of file