Refactor connection logic and add ViewExtensions for cleaner data context access

This commit is contained in:
Stone_Red
2026-07-16 01:45:05 +02:00
parent 6d9c4c6c5c
commit 66c1615774
5 changed files with 109 additions and 119 deletions
+62 -92
View File
@@ -35,61 +35,10 @@ public sealed class ConnectionService : IDisposable
private readonly Dictionary<string, ServerConnection> _connections = new(StringComparer.OrdinalIgnoreCase);
internal IReadOnlyDictionary<string, ServerConnection> Connections => _connections;
public async Task<ServerModel> ConnectAsync(string serverUrl, string username, string password, bool isRegister, bool rememberMe)
private async Task<ServerModel> 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<ChannelModel> 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<ChannelModel> 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<ServerModel> 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<ServerConnection?> 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<ClientConfig, SavedServer?> 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)
+17 -17
View File
@@ -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<ConnectDialogResult?>(_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<ConnectDialogResult?> 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<ConnectDialogResult?>(_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<ConnectDialogResult?>(_mainWindow);
ConnectDialogResult? result = await ShowConnectDialogAsync(prefill);
if (result is null)
{
return;
+4 -2
View File
@@ -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<MessageComposerViewModel>();
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<MessageComposerViewModel>();
if (vm is null)
{
return;
}
+11 -8
View File
@@ -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<MessageViewModel>();
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<MessageViewModel>();
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;
}
+15
View File
@@ -0,0 +1,15 @@
using Avalonia;
using Avalonia.Controls;
using Decho.ViewModels;
namespace Decho.Views;
internal static class ViewExtensions
{
public static T? GetDataContext<T>(this Control control) where T : class
=> control.DataContext as T;
public static MainWindowViewModel? GetMainWindowViewModel(this Control control)
=> TopLevel.GetTopLevel(control)?.DataContext as MainWindowViewModel;
}