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); private readonly Dictionary<string, ServerConnection> _connections = new(StringComparer.OrdinalIgnoreCase);
internal IReadOnlyDictionary<string, ServerConnection> Connections => _connections; 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(); 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; ConnectResult result;
try try
{ {
@@ -107,9 +56,9 @@ public sealed class ConnectionService : IDisposable
ObservableCollection<ChannelModel> channels = []; ObservableCollection<ChannelModel> channels = [];
ServerModel serverModel = new ServerModel( ServerModel serverModel = new ServerModel(
Guid.NewGuid().ToString("N"), Guid.NewGuid().ToString("N"),
new Uri(serverUrl).Host, new Uri(dialogResult.ServerUrl).Host,
channels, channels,
serverUrl, dialogResult.ServerUrl,
isConnected: true, isConnected: true,
connectedUser: login.Username); connectedUser: login.Username);
@@ -123,16 +72,32 @@ public sealed class ConnectionService : IDisposable
WireConnectionEvents(serverEntry, conn); WireConnectionEvents(serverEntry, conn);
_connections[serverUrl] = serverEntry; _connections[dialogResult.ServerUrl] = serverEntry;
SaveRefreshToken(serverUrl, rememberMe); SaveRefreshToken(dialogResult.ServerUrl, dialogResult.RememberMe);
ServerAdded?.Invoke(serverModel); 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)) if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
{ {
return; return null;
} }
entry.Server.IsConnected = false; entry.Server.IsConnected = false;
@@ -143,37 +108,43 @@ public sealed class ConnectionService : IDisposable
await entry.Manager.DisposeAsync(); await entry.Manager.DisposeAsync();
_ = _connections.Remove(serverUrl); _ = _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) public async Task RemoveServerAsync(string serverUrl)
{ {
if (_connections.TryGetValue(serverUrl, out ServerConnection? entry)) await CleanupConnectionAsync(serverUrl);
{
entry.Server.IsConnected = false;
entry.Server.IsConnecting = false;
await entry.Manager.CleanupAsync();
entry.ApiClient.Dispose();
await entry.Manager.DisposeAsync();
_ = _connections.Remove(serverUrl);
}
RemoveServerFromConfig(serverUrl); RemoveServerFromConfig(serverUrl);
ServerRemoved?.Invoke(serverUrl); ServerRemoved?.Invoke(serverUrl);
} }
private static void RemoveServerFromConfig(string serverUrl) private static void ModifyConfig(string serverUrl, Action<ClientConfig, SavedServer?> action)
{ {
ClientConfig config = ConfigManager.Load(); ClientConfig config = ConfigManager.Load();
SavedServer? saved = config.SavedServers.FirstOrDefault(s => SavedServer? saved = config.SavedServers.FirstOrDefault(s =>
string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase)); 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); if (saved is not null)
ConfigManager.Save(config); {
} _ = config.SavedServers.Remove(saved);
}
});
} }
public async Task SendMessageAsync(string serverUrl, string channelName, string content) public async Task SendMessageAsync(string serverUrl, string channelName, string content)
@@ -554,25 +525,24 @@ public sealed class ConnectionService : IDisposable
return; return;
} }
ClientConfig config = ConfigManager.Load(); ModifyConfig(serverUrl, (config, saved) =>
SavedServer? saved = config.SavedServers.FirstOrDefault(s =>
string.Equals(s.Url, serverUrl, StringComparison.OrdinalIgnoreCase));
if (saved is null)
{ {
saved = new SavedServer if (saved is null)
{ {
Name = new Uri(serverUrl).Host, saved = new SavedServer
Url = serverUrl, {
Username = entry.User.Id, Name = new Uri(serverUrl).Host,
RememberMe = true, Url = serverUrl,
LastConnected = DateTimeOffset.Now, Username = entry.User.Id,
}; RememberMe = true,
config.SavedServers.Add(saved); LastConnected = DateTimeOffset.Now,
} };
config.SavedServers.Add(saved);
}
saved.RefreshToken = token; saved.RefreshToken = token;
saved.LastConnected = DateTimeOffset.Now; saved.LastConnected = DateTimeOffset.Now;
ConfigManager.Save(config); });
} }
private void WireConnectionEvents(ServerConnection entry, ConnectionManager conn) private void WireConnectionEvents(ServerConnection entry, ConnectionManager conn)
+17 -17
View File
@@ -74,14 +74,7 @@ public sealed class MainWindowViewModel : ViewModelBase
private async void AddServer() private async void AddServer()
{ {
if (_mainWindow is null) ConnectDialogResult? result = await ShowConnectDialogAsync(null);
{
return;
}
ClientConfig config = ConfigManager.Load();
ConnectDialogWindow dialog = new ConnectDialogWindow(config.SavedServers);
ConnectDialogResult? result = await dialog.ShowDialog<ConnectDialogResult?>(_mainWindow);
if (result is null) if (result is null)
{ {
return; return;
@@ -129,6 +122,20 @@ public sealed class MainWindowViewModel : ViewModelBase
ConfigManager.SaveServer(savedServer); 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() private void WireCommandHandlerEvents()
{ {
_commandHandler.OnSetStatus += async (status, message) => _commandHandler.OnSetStatus += async (status, message) =>
@@ -670,16 +677,9 @@ public sealed class MainWindowViewModel : ViewModelBase
private async Task HandleServerConnectRequested(ServerViewModel serverVm) private async Task HandleServerConnectRequested(ServerViewModel serverVm)
{ {
if (_mainWindow is null) SavedServer? prefill = ConfigManager.Load().SavedServers.FirstOrDefault(s =>
{
return;
}
ClientConfig config = ConfigManager.Load();
SavedServer? prefill = config.SavedServers.FirstOrDefault(s =>
string.Equals(s.Url, serverVm.ServerUrl, StringComparison.OrdinalIgnoreCase)); string.Equals(s.Url, serverVm.ServerUrl, StringComparison.OrdinalIgnoreCase));
ConnectDialogWindow dialog = new ConnectDialogWindow(config.SavedServers, prefill); ConnectDialogResult? result = await ShowConnectDialogAsync(prefill);
ConnectDialogResult? result = await dialog.ShowDialog<ConnectDialogResult?>(_mainWindow);
if (result is null) if (result is null)
{ {
return; return;
+4 -2
View File
@@ -19,7 +19,8 @@ public partial class MessageComposerView : UserControl
private async void OnFileUploadClicked(object? sender, RoutedEventArgs e) private async void OnFileUploadClicked(object? sender, RoutedEventArgs e)
{ {
if (DataContext is not MessageComposerViewModel vm) MessageComposerViewModel? vm = this.GetDataContext<MessageComposerViewModel>();
if (vm is null)
{ {
return; return;
} }
@@ -55,7 +56,8 @@ public partial class MessageComposerView : UserControl
private void OnDrop(object? sender, DragEventArgs e) private void OnDrop(object? sender, DragEventArgs e)
{ {
if (DataContext is not MessageComposerViewModel vm) MessageComposerViewModel? vm = this.GetDataContext<MessageComposerViewModel>();
if (vm is null)
{ {
return; return;
} }
+11 -8
View File
@@ -44,8 +44,8 @@ public partial class MessageItemView : UserControl
{ {
try try
{ {
TopLevel? topLevel = TopLevel.GetTopLevel(this); MainWindowViewModel? mainVm = this.GetMainWindowViewModel();
if (topLevel?.DataContext is not MainWindowViewModel mainVm) if (mainVm is null)
{ {
return; return;
} }
@@ -73,13 +73,14 @@ public partial class MessageItemView : UserControl
private async void OnAuthorNamePointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e) 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; return;
} }
TopLevel? topLevel = TopLevel.GetTopLevel(this); MainWindowViewModel? mainVm = this.GetMainWindowViewModel();
if (topLevel?.DataContext is not MainWindowViewModel mainVm) if (mainVm is null)
{ {
return; return;
} }
@@ -94,7 +95,7 @@ public partial class MessageItemView : UserControl
ProfileWindow dialog = new ProfileWindow(profile); ProfileWindow dialog = new ProfileWindow(profile);
if (topLevel is Window parent) if (TopLevel.GetTopLevel(this) is Window parent)
{ {
await dialog.ShowDialog(parent); await dialog.ShowDialog(parent);
} }
@@ -102,7 +103,8 @@ public partial class MessageItemView : UserControl
private async void OnDownloadClicked(object? sender, RoutedEventArgs e) private async void OnDownloadClicked(object? sender, RoutedEventArgs e)
{ {
if (DataContext is not MessageViewModel msg) MessageViewModel? msg = this.GetDataContext<MessageViewModel>();
if (msg is null)
{ {
return; return;
} }
@@ -118,7 +120,8 @@ public partial class MessageItemView : UserControl
return; return;
} }
if (topLevel.DataContext is not MainWindowViewModel mainVm) MainWindowViewModel? mainVm = this.GetMainWindowViewModel();
if (mainVm is null)
{ {
return; 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;
}