From 31a2d874ef35e331e1dfb4fb364611a2a5dbac6c Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 15 Jul 2026 23:49:51 +0200 Subject: [PATCH] Add option to remove server and disable input TextBox on channels of disconnected servers --- src/Decho/Services/ConnectionService.cs | 31 ++++++++++- src/Decho/ViewModels/ChatViewModel.cs | 13 +---- src/Decho/ViewModels/MainWindowViewModel.cs | 30 +++++++++- .../ViewModels/MessageComposerViewModel.cs | 9 ++- src/Decho/ViewModels/ServerViewModel.cs | 19 +++++++ src/Decho/Views/MessageComposerView.axaml | 9 ++- src/Decho/Views/SidebarView.axaml | 55 ++++++++++++------- src/Decho/Views/SidebarView.axaml.cs | 9 +++ 8 files changed, 137 insertions(+), 38 deletions(-) diff --git a/src/Decho/Services/ConnectionService.cs b/src/Decho/Services/ConnectionService.cs index 542f7f2..6819698 100644 --- a/src/Decho/Services/ConnectionService.cs +++ b/src/Decho/Services/ConnectionService.cs @@ -137,16 +137,45 @@ public sealed class ConnectionService : IDisposable entry.Server.IsConnected = false; entry.Server.IsConnecting = false; - ServerStateChanged?.Invoke(entry.Server); await entry.Manager.CleanupAsync(); entry.ApiClient.Dispose(); await entry.Manager.DisposeAsync(); _ = _connections.Remove(serverUrl); + ServerStateChanged?.Invoke(entry.Server); + } + + public async Task RemoveServerAsync(string serverUrl) + { + if (_connections.TryGetValue(serverUrl, out ServerConnection? entry)) + { + entry.Server.IsConnected = false; + entry.Server.IsConnecting = false; + + await entry.Manager.CleanupAsync(); + entry.ApiClient.Dispose(); + await entry.Manager.DisposeAsync(); + + _ = _connections.Remove(serverUrl); + } + + RemoveServerFromConfig(serverUrl); ServerRemoved?.Invoke(serverUrl); } + private static void RemoveServerFromConfig(string serverUrl) + { + ClientConfig config = ConfigManager.Load(); + SavedServer? saved = config.SavedServers.FirstOrDefault(s => + string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase)); + if (saved is not null) + { + _ = config.SavedServers.Remove(saved); + ConfigManager.Save(config); + } + } + public async Task SendMessageAsync(string serverUrl, string channelName, string content) { if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry)) diff --git a/src/Decho/ViewModels/ChatViewModel.cs b/src/Decho/ViewModels/ChatViewModel.cs index c6c47c3..a4d37d2 100644 --- a/src/Decho/ViewModels/ChatViewModel.cs +++ b/src/Decho/ViewModels/ChatViewModel.cs @@ -1,5 +1,3 @@ -using EchoHub.Client.Commands; - using System.Collections.ObjectModel; namespace Decho.ViewModels; @@ -45,7 +43,7 @@ public sealed class ChatViewModel : ViewModelBase Composer = new MessageComposerViewModel(); } - public void SetChannel(ChannelViewModel? channel, string serverUrl = "") + public void SetChannel(ChannelViewModel? channel, string serverUrl = "", bool isServerConnected = true) { if (channel is null) { @@ -55,7 +53,7 @@ public sealed class ChatViewModel : ViewModelBase HasTopic = false; CurrentChannelName = string.Empty; CurrentServerUrl = string.Empty; - Composer.SetServer(string.Empty); + Composer.SetServer(string.Empty, isServerConnected); return; } @@ -65,12 +63,7 @@ public sealed class ChatViewModel : ViewModelBase HasTopic = channel.HasTopic; CurrentChannelName = channel.Name; CurrentServerUrl = serverUrl; - Composer.SetServer(serverUrl); - } - - public void SetComposerCommandHandler(CommandHandler handler) - { - Composer.SetCommandHandler(handler); + Composer.SetServer(serverUrl, isServerConnected); } public void ClearMessages() diff --git a/src/Decho/ViewModels/MainWindowViewModel.cs b/src/Decho/ViewModels/MainWindowViewModel.cs index 141255d..d96b42c 100644 --- a/src/Decho/ViewModels/MainWindowViewModel.cs +++ b/src/Decho/ViewModels/MainWindowViewModel.cs @@ -79,6 +79,7 @@ public sealed class MainWindowViewModel : ViewModelBase ServerViewModel serverVm = new ServerViewModel(placeholderServer); serverVm.ConnectRequested += async () => await HandleServerConnectRequested(serverVm); serverVm.DisconnectRequested += async () => await HandleServerDisconnectRequested(serverVm); + serverVm.RemoveRequested += async () => await HandleServerRemoveRequested(serverVm); Sidebar.Servers.Add(serverVm); StatusText = "Click Connect on the server to get started"; @@ -352,6 +353,7 @@ public sealed class MainWindowViewModel : ViewModelBase ServerViewModel serverVm = new ServerViewModel(server); serverVm.ConnectRequested += () => HandleServerConnectRequested(serverVm); serverVm.DisconnectRequested += () => HandleServerDisconnectRequested(serverVm); + serverVm.RemoveRequested += () => HandleServerRemoveRequested(serverVm); _ = serverVm.WhenAnyValue(s => s.SelectedChannel) .Where(channel => channel is not null) .Subscribe(channel => Sidebar.SelectedChannel = channel!); @@ -365,6 +367,11 @@ public sealed class MainWindowViewModel : ViewModelBase } Sidebar.Servers.Add(serverVm); + if (serverVm.Channels.Count > 0) + { + serverVm.SelectedChannel = serverVm.Channels[0]; + } + StatusText = $"Connected to {server.Name}"; }); }; @@ -390,6 +397,11 @@ public sealed class MainWindowViewModel : ViewModelBase if (serverVm is not null) { serverVm.SyncFromModel(); + if (string.Equals(server.ServerUrl, Chat.CurrentServerUrl, StringComparison.OrdinalIgnoreCase)) + { + Chat.Composer.IsConnected = server.IsConnected; + } + if (!server.IsConnected) { StatusText = $"Disconnected from {server.Name}"; @@ -493,11 +505,12 @@ public sealed class MainWindowViewModel : ViewModelBase } string serverUrl = FindServerUrlForChannel(channel); - Chat.SetChannel(channel, serverUrl); + bool isServerConnected = Sidebar.GetServer(serverUrl)?.IsConnected ?? false; + Chat.SetChannel(channel, serverUrl, isServerConnected); if (!string.IsNullOrEmpty(serverUrl)) { - Chat.SetComposerCommandHandler(new CommandHandler()); + Chat.Composer.SetCommandHandler(new CommandHandler()); _ = Task.Run(async () => { @@ -538,6 +551,7 @@ public sealed class MainWindowViewModel : ViewModelBase ServerViewModel serverVm = new ServerViewModel(placeholderServer); serverVm.ConnectRequested += async () => await HandleServerConnectRequested(serverVm); serverVm.DisconnectRequested += async () => await HandleServerDisconnectRequested(serverVm); + serverVm.RemoveRequested += async () => await HandleServerRemoveRequested(serverVm); Avalonia.Threading.Dispatcher.UIThread.Post(() => { @@ -642,6 +656,18 @@ public sealed class MainWindowViewModel : ViewModelBase } } + private async Task HandleServerRemoveRequested(ServerViewModel serverVm) + { + try + { + await ConnectionService.RemoveServerAsync(serverVm.ServerUrl); + } + catch (Exception ex) + { + StatusText = $"Remove error: {ex.Message}"; + } + } + private void EnsureChannelInList(string serverUrl, string channelName) { ServerViewModel? serverVm = Sidebar.GetServer(serverUrl); diff --git a/src/Decho/ViewModels/MessageComposerViewModel.cs b/src/Decho/ViewModels/MessageComposerViewModel.cs index 6d994c8..a526956 100644 --- a/src/Decho/ViewModels/MessageComposerViewModel.cs +++ b/src/Decho/ViewModels/MessageComposerViewModel.cs @@ -22,6 +22,12 @@ public sealed class MessageComposerViewModel : ViewModelBase public string ServerUrl { get; private set; } = string.Empty; + public bool IsConnected + { + get; + set => this.RaiseAndSetIfChanged(ref field, value); + } = true; + public ReactiveCommand SendCommand { get; } public bool HasCommandHandler => _commandHandler is not null; @@ -40,9 +46,10 @@ public sealed class MessageComposerViewModel : ViewModelBase } } - public void SetServer(string serverUrl) + public void SetServer(string serverUrl, bool isConnected = true) { ServerUrl = serverUrl; + IsConnected = isConnected; } public void SetCommandHandler(CommandHandler handler) diff --git a/src/Decho/ViewModels/ServerViewModel.cs b/src/Decho/ViewModels/ServerViewModel.cs index 23b06ac..85e9c58 100644 --- a/src/Decho/ViewModels/ServerViewModel.cs +++ b/src/Decho/ViewModels/ServerViewModel.cs @@ -19,6 +19,12 @@ public sealed class ServerViewModel : ViewModelBase remove => _disconnectRequested = (Func?)Delegate.Remove(_disconnectRequested, value); } + public event Func? RemoveRequested + { + add => _removeRequested = (Func?)Delegate.Combine(_removeRequested, value); + remove => _removeRequested = (Func?)Delegate.Remove(_removeRequested, value); + } + private bool _isConnected; private bool _isConnecting; private string? _connectedUser; @@ -27,6 +33,8 @@ public sealed class ServerViewModel : ViewModelBase private Func? _disconnectRequested; + private Func? _removeRequested; + public ServerModel Model { get; } public string Name => Model.Name; @@ -39,6 +47,8 @@ public sealed class ServerViewModel : ViewModelBase public ReactiveCommand DisconnectCommand { get; } + public ReactiveCommand RemoveCommand { get; } + public ChannelViewModel? SelectedChannel { get; @@ -124,6 +134,7 @@ public sealed class ServerViewModel : ViewModelBase ConnectCommand = ReactiveCommand.CreateFromTask(ConnectAsync); DisconnectCommand = ReactiveCommand.CreateFromTask(DisconnectAsync); + RemoveCommand = ReactiveCommand.CreateFromTask(RemoveAsync); } public void SyncFromModel() @@ -149,4 +160,12 @@ public sealed class ServerViewModel : ViewModelBase await _disconnectRequested(); } } + + private async Task RemoveAsync() + { + if (_removeRequested is not null) + { + await _removeRequested(); + } + } } \ No newline at end of file diff --git a/src/Decho/Views/MessageComposerView.axaml b/src/Decho/Views/MessageComposerView.axaml index 4e1f81f..7a638a0 100644 --- a/src/Decho/Views/MessageComposerView.axaml +++ b/src/Decho/Views/MessageComposerView.axaml @@ -8,7 +8,8 @@ + Text="{Binding Draft, Mode=TwoWay}" + IsEnabled="{Binding IsConnected}"> @@ -19,11 +20,13 @@ Margin="0 0 5 0" Content="Send" i:Attached.Icon="fa-paper-plane" - Command="{Binding SendCommand}" /> + Command="{Binding SendCommand}" + IsEnabled="{Binding IsConnected}" /> +