From 66c1615774343be90c1c6f9f7489e1642f8c4bbe Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:45:05 +0200 Subject: [PATCH] Refactor connection logic and add ViewExtensions for cleaner data context access --- src/Decho/Services/ConnectionService.cs | 154 ++++++++----------- src/Decho/ViewModels/MainWindowViewModel.cs | 34 ++-- src/Decho/Views/MessageComposerView.axaml.cs | 6 +- src/Decho/Views/MessageItemView.axaml.cs | 19 ++- src/Decho/Views/ViewExtensions.cs | 15 ++ 5 files changed, 109 insertions(+), 119 deletions(-) create mode 100644 src/Decho/Views/ViewExtensions.cs diff --git a/src/Decho/Services/ConnectionService.cs b/src/Decho/Services/ConnectionService.cs index 0997c2d..0974566 100644 --- a/src/Decho/Services/ConnectionService.cs +++ b/src/Decho/Services/ConnectionService.cs @@ -35,61 +35,10 @@ public sealed class ConnectionService : IDisposable private readonly Dictionary _connections = new(StringComparer.OrdinalIgnoreCase); internal IReadOnlyDictionary Connections => _connections; - public async Task ConnectAsync(string serverUrl, string username, string password, bool isRegister, bool rememberMe) + private async Task ConnectCoreAsync(ConnectDialogResult dialogResult) { ConnectionManager conn = new ConnectionManager(); - ConnectDialogResult dialogResult = new EchoHub.Client.UI.Dialogs.ConnectDialogResult( - serverUrl, username, password, isRegister, rememberMe, null); - - 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(serverUrl).Host, - channels, - 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[serverUrl] = serverEntry; - SaveRefreshToken(serverUrl, rememberMe); - ServerAdded?.Invoke(serverModel); - - return serverModel; - } - - public async Task ConnectWithSavedTokenAsync(string serverUrl, string username, string refreshToken, bool rememberMe) - { - ConnectionManager conn = new ConnectionManager(); - - ConnectDialogResult dialogResult = new EchoHub.Client.UI.Dialogs.ConnectDialogResult( - serverUrl, username, "", false, rememberMe, refreshToken); - ConnectResult result; try { @@ -107,9 +56,9 @@ public sealed class ConnectionService : IDisposable ObservableCollection channels = []; ServerModel serverModel = new ServerModel( Guid.NewGuid().ToString("N"), - new Uri(serverUrl).Host, + new Uri(dialogResult.ServerUrl).Host, channels, - serverUrl, + dialogResult.ServerUrl, isConnected: true, connectedUser: login.Username); @@ -123,16 +72,32 @@ public sealed class ConnectionService : IDisposable WireConnectionEvents(serverEntry, conn); - _connections[serverUrl] = serverEntry; - SaveRefreshToken(serverUrl, rememberMe); + _connections[dialogResult.ServerUrl] = serverEntry; + SaveRefreshToken(dialogResult.ServerUrl, dialogResult.RememberMe); ServerAdded?.Invoke(serverModel); + + return serverModel; } - public async Task DisconnectAsync(string serverUrl) + public async Task ConnectAsync(string serverUrl, string username, string password, bool isRegister, bool rememberMe) + { + ConnectDialogResult dialogResult = new ConnectDialogResult( + serverUrl, username, password, isRegister, rememberMe, null); + return await ConnectCoreAsync(dialogResult); + } + + public async Task ConnectWithSavedTokenAsync(string serverUrl, string username, string refreshToken, bool rememberMe) + { + ConnectDialogResult dialogResult = new ConnectDialogResult( + serverUrl, username, "", false, rememberMe, refreshToken); + await ConnectCoreAsync(dialogResult); + } + + private async Task CleanupConnectionAsync(string serverUrl) { if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry)) { - return; + return null; } entry.Server.IsConnected = false; @@ -143,37 +108,43 @@ public sealed class ConnectionService : IDisposable await entry.Manager.DisposeAsync(); _ = _connections.Remove(serverUrl); - ServerStateChanged?.Invoke(entry.Server); + return entry; + } + + public async Task DisconnectAsync(string serverUrl) + { + ServerConnection? entry = await CleanupConnectionAsync(serverUrl); + if (entry is not null) + { + 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); - } - + await CleanupConnectionAsync(serverUrl); RemoveServerFromConfig(serverUrl); ServerRemoved?.Invoke(serverUrl); } - private static void RemoveServerFromConfig(string 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)); - if (saved is not null) + action(config, saved); + ConfigManager.Save(config); + } + + private static void RemoveServerFromConfig(string serverUrl) + { + ModifyConfig(serverUrl, (config, saved) => { - _ = config.SavedServers.Remove(saved); - ConfigManager.Save(config); - } + if (saved is not null) + { + _ = config.SavedServers.Remove(saved); + } + }); } public async Task SendMessageAsync(string serverUrl, string channelName, string content) @@ -554,25 +525,24 @@ public sealed class ConnectionService : IDisposable return; } - ClientConfig config = ConfigManager.Load(); - SavedServer? saved = config.SavedServers.FirstOrDefault(s => - string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase)); - if (saved is null) + ModifyConfig(serverUrl, (config, saved) => { - saved = new SavedServer + if (saved is null) { - Name = new Uri(serverUrl).Host, - Url = serverUrl, - Username = entry.User.Id, - RememberMe = true, - LastConnected = DateTimeOffset.Now, - }; - config.SavedServers.Add(saved); - } + saved = new SavedServer + { + Name = new Uri(serverUrl).Host, + Url = serverUrl, + Username = entry.User.Id, + RememberMe = true, + LastConnected = DateTimeOffset.Now, + }; + config.SavedServers.Add(saved); + } - saved.RefreshToken = token; - saved.LastConnected = DateTimeOffset.Now; - ConfigManager.Save(config); + saved.RefreshToken = token; + saved.LastConnected = DateTimeOffset.Now; + }); } private void WireConnectionEvents(ServerConnection entry, ConnectionManager conn) diff --git a/src/Decho/ViewModels/MainWindowViewModel.cs b/src/Decho/ViewModels/MainWindowViewModel.cs index b9118ab..07beba7 100644 --- a/src/Decho/ViewModels/MainWindowViewModel.cs +++ b/src/Decho/ViewModels/MainWindowViewModel.cs @@ -74,14 +74,7 @@ public sealed class MainWindowViewModel : ViewModelBase private async void AddServer() { - if (_mainWindow is null) - { - return; - } - - ClientConfig config = ConfigManager.Load(); - ConnectDialogWindow dialog = new ConnectDialogWindow(config.SavedServers); - ConnectDialogResult? result = await dialog.ShowDialog(_mainWindow); + ConnectDialogResult? result = await ShowConnectDialogAsync(null); if (result is null) { return; @@ -129,6 +122,20 @@ public sealed class MainWindowViewModel : ViewModelBase ConfigManager.SaveServer(savedServer); } + private async Task ShowConnectDialogAsync(SavedServer? prefill) + { + if (_mainWindow is null) + { + return null; + } + + ClientConfig config = ConfigManager.Load(); + ConnectDialogWindow dialog = prefill is null + ? new ConnectDialogWindow(config.SavedServers) + : new ConnectDialogWindow(config.SavedServers, prefill); + return await dialog.ShowDialog(_mainWindow); + } + private void WireCommandHandlerEvents() { _commandHandler.OnSetStatus += async (status, message) => @@ -670,16 +677,9 @@ public sealed class MainWindowViewModel : ViewModelBase private async Task HandleServerConnectRequested(ServerViewModel serverVm) { - if (_mainWindow is null) - { - return; - } - - ClientConfig config = ConfigManager.Load(); - SavedServer? prefill = config.SavedServers.FirstOrDefault(s => + SavedServer? prefill = ConfigManager.Load().SavedServers.FirstOrDefault(s => string.Equals(s.Url, serverVm.ServerUrl, StringComparison.OrdinalIgnoreCase)); - ConnectDialogWindow dialog = new ConnectDialogWindow(config.SavedServers, prefill); - ConnectDialogResult? result = await dialog.ShowDialog(_mainWindow); + ConnectDialogResult? result = await ShowConnectDialogAsync(prefill); if (result is null) { return; diff --git a/src/Decho/Views/MessageComposerView.axaml.cs b/src/Decho/Views/MessageComposerView.axaml.cs index 0c54b45..2a6bc25 100644 --- a/src/Decho/Views/MessageComposerView.axaml.cs +++ b/src/Decho/Views/MessageComposerView.axaml.cs @@ -19,7 +19,8 @@ public partial class MessageComposerView : UserControl private async void OnFileUploadClicked(object? sender, RoutedEventArgs e) { - if (DataContext is not MessageComposerViewModel vm) + MessageComposerViewModel? vm = this.GetDataContext(); + if (vm is null) { return; } @@ -55,7 +56,8 @@ public partial class MessageComposerView : UserControl private void OnDrop(object? sender, DragEventArgs e) { - if (DataContext is not MessageComposerViewModel vm) + MessageComposerViewModel? vm = this.GetDataContext(); + if (vm is null) { return; } diff --git a/src/Decho/Views/MessageItemView.axaml.cs b/src/Decho/Views/MessageItemView.axaml.cs index 34322f8..65e02ee 100644 --- a/src/Decho/Views/MessageItemView.axaml.cs +++ b/src/Decho/Views/MessageItemView.axaml.cs @@ -44,8 +44,8 @@ public partial class MessageItemView : UserControl { try { - TopLevel? topLevel = TopLevel.GetTopLevel(this); - if (topLevel?.DataContext is not MainWindowViewModel mainVm) + MainWindowViewModel? mainVm = this.GetMainWindowViewModel(); + if (mainVm is null) { return; } @@ -73,13 +73,14 @@ public partial class MessageItemView : UserControl private async void OnAuthorNamePointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e) { - if (DataContext is not MessageViewModel msg) + MessageViewModel? msg = this.GetDataContext(); + if (msg is null) { return; } - TopLevel? topLevel = TopLevel.GetTopLevel(this); - if (topLevel?.DataContext is not MainWindowViewModel mainVm) + MainWindowViewModel? mainVm = this.GetMainWindowViewModel(); + if (mainVm is null) { return; } @@ -94,7 +95,7 @@ public partial class MessageItemView : UserControl ProfileWindow dialog = new ProfileWindow(profile); - if (topLevel is Window parent) + if (TopLevel.GetTopLevel(this) is Window parent) { await dialog.ShowDialog(parent); } @@ -102,7 +103,8 @@ public partial class MessageItemView : UserControl private async void OnDownloadClicked(object? sender, RoutedEventArgs e) { - if (DataContext is not MessageViewModel msg) + MessageViewModel? msg = this.GetDataContext(); + if (msg is null) { return; } @@ -118,7 +120,8 @@ public partial class MessageItemView : UserControl return; } - if (topLevel.DataContext is not MainWindowViewModel mainVm) + MainWindowViewModel? mainVm = this.GetMainWindowViewModel(); + if (mainVm is null) { return; } diff --git a/src/Decho/Views/ViewExtensions.cs b/src/Decho/Views/ViewExtensions.cs new file mode 100644 index 0000000..96d356a --- /dev/null +++ b/src/Decho/Views/ViewExtensions.cs @@ -0,0 +1,15 @@ +using Avalonia; +using Avalonia.Controls; + +using Decho.ViewModels; + +namespace Decho.Views; + +internal static class ViewExtensions +{ + public static T? GetDataContext(this Control control) where T : class + => control.DataContext as T; + + public static MainWindowViewModel? GetMainWindowViewModel(this Control control) + => TopLevel.GetTopLevel(control)?.DataContext as MainWindowViewModel; +}