Code cleanup

This commit is contained in:
Stone_Red
2026-07-16 20:08:31 +02:00
parent 5a0ff2ddfe
commit 019fb3d462
14 changed files with 171 additions and 205 deletions
+129 -129
View File
@@ -35,86 +35,6 @@ 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;
private async Task<ServerModel> 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<ChannelModel> 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<ChannelDto> channels)
{
ClientConfig config = ConfigManager.Load();
List<string> 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<ServerModel> ConnectAsync(string serverUrl, string username, string password, bool isRegister, bool rememberMe) public async Task<ServerModel> ConnectAsync(string serverUrl, string username, string password, bool isRegister, bool rememberMe)
{ {
ConnectDialogResult dialogResult = new ConnectDialogResult( ConnectDialogResult dialogResult = new ConnectDialogResult(
@@ -129,24 +49,6 @@ public sealed class ConnectionService : IDisposable
_ = await ConnectCoreAsync(dialogResult); _ = await ConnectCoreAsync(dialogResult);
} }
private async Task<ServerConnection?> 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) public async Task DisconnectAsync(string serverUrl)
{ {
ServerConnection? entry = await CleanupConnectionAsync(serverUrl); ServerConnection? entry = await CleanupConnectionAsync(serverUrl);
@@ -163,26 +65,6 @@ public sealed class ConnectionService : IDisposable
ServerRemoved?.Invoke(serverUrl); ServerRemoved?.Invoke(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));
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) public async Task SendMessageAsync(string serverUrl, string channelName, string content)
{ {
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry)) 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<List<MessageModel>> GetHistoryAsync(string serverUrl, string channelName, int count = HubConstants.DefaultHistoryCount, int offset = 0) public async Task<List<MessageModel>> GetHistoryAsync(string serverUrl, string channelName, int count = HubConstants.DefaultHistoryCount, int offset = 0)
{ {
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry)) 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; return _connections.TryGetValue(serverUrl, out ServerConnection? conn) ? conn : null;
} }
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));
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<ServerModel> 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<ChannelModel> 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<ChannelDto> channels)
{
ClientConfig config = ConfigManager.Load();
List<string> 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<ServerConnection?> 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) private void SaveRefreshToken(string serverUrl, bool rememberMe)
{ {
if (!rememberMe) if (!rememberMe)
+5 -12
View File
@@ -3,29 +3,27 @@ using System.Text.RegularExpressions;
namespace Decho.ViewModels; namespace Decho.ViewModels;
public sealed class AutocompleteController : ViewModelBase public sealed class AutocompleteController(IEnumerable<AutocompleteProvider> providers) : ViewModelBase
{ {
private readonly List<AutocompleteProvider> _providers;
private static readonly Regex TriggerPattern = new(@"([@#])(\w*)$", RegexOptions.Compiled); private static readonly Regex TriggerPattern = new(@"([@#])(\w*)$", RegexOptions.Compiled);
private readonly List<AutocompleteProvider> _providers = providers.ToList();
public ObservableCollection<string> FilteredItems { get; } = []; public ObservableCollection<string> FilteredItems { get; } = [];
public bool ShowPopup public bool ShowPopup
{ {
get => field; get;
set => this.RaiseAndSetIfChanged(ref field, value); set => this.RaiseAndSetIfChanged(ref field, value);
} }
public int SelectedIndex public int SelectedIndex
{ {
get => field; get;
set => this.RaiseAndSetIfChanged(ref field, value); set => this.RaiseAndSetIfChanged(ref field, value);
} }
public string FilterText public string FilterText
{ {
get => field; get;
set => this.RaiseAndSetIfChanged(ref field, value); set => this.RaiseAndSetIfChanged(ref field, value);
} }
@@ -33,11 +31,6 @@ public sealed class AutocompleteController : ViewModelBase
public int TriggerCharIndex { get; private set; } public int TriggerCharIndex { get; private set; }
public AutocompleteController(IEnumerable<AutocompleteProvider> providers)
{
_providers = providers.ToList();
}
public void Update(string text) public void Update(string text)
{ {
if (string.IsNullOrEmpty(text) || _providers.Count == 0) if (string.IsNullOrEmpty(text) || _providers.Count == 0)
+6 -19
View File
@@ -1,23 +1,10 @@
namespace Decho.ViewModels; namespace Decho.ViewModels;
public sealed class AutocompleteProvider public sealed class AutocompleteProvider(char trigger, Func<IEnumerable<string>> itemsSource, string insertPrefix, int maxResults = 10)
{ {
public char Trigger { get; } public char Trigger { get; } = trigger;
public Func<IEnumerable<string>> ItemsSource { get; } public Func<IEnumerable<string>> ItemsSource { get; } = itemsSource;
public Func<string, string, bool> Filter { get; } public Func<string, string, bool> Filter { get; } = (item, filter) => item.StartsWith(filter, StringComparison.OrdinalIgnoreCase);
public Func<string, string> FormatInsertion { get; } public Func<string, string> FormatInsertion { get; } = item => $"{insertPrefix}{item} ";
public int MaxResults { get; } public int MaxResults { get; } = maxResults;
public AutocompleteProvider(
char trigger,
Func<IEnumerable<string>> itemsSource,
string insertPrefix,
int maxResults = 10)
{
Trigger = trigger;
ItemsSource = itemsSource;
Filter = (item, filter) => item.StartsWith(filter, StringComparison.OrdinalIgnoreCase);
FormatInsertion = item => $"{insertPrefix}{item} ";
MaxResults = maxResults;
}
} }
+3 -3
View File
@@ -34,13 +34,13 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase
public bool IsLocked public bool IsLocked
{ {
get => field; get;
set => this.RaiseAndSetIfChanged(ref field, value); set => this.RaiseAndSetIfChanged(ref field, value);
} }
public int UnreadCount public int UnreadCount
{ {
get => field; get;
set set
{ {
if (field == value) if (field == value)
@@ -60,7 +60,7 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase
public int MentionCount public int MentionCount
{ {
get => field; get;
set set
{ {
if (field == value) if (field == value)
+7 -7
View File
@@ -39,7 +39,7 @@ public sealed class ChatViewModel : ViewModelBase
public bool ShowOnlineUsers public bool ShowOnlineUsers
{ {
get => field; get;
set => this.RaiseAndSetIfChanged(ref field, value); set => this.RaiseAndSetIfChanged(ref field, value);
} }
@@ -47,7 +47,7 @@ public sealed class ChatViewModel : ViewModelBase
public string OnlineUserCount public string OnlineUserCount
{ {
get => field; get;
set => this.RaiseAndSetIfChanged(ref field, value); set => this.RaiseAndSetIfChanged(ref field, value);
} = string.Empty; } = string.Empty;
@@ -63,11 +63,6 @@ public sealed class ChatViewModel : ViewModelBase
ToggleUsersPanelCommand = ReactiveCommand.Create(ToggleUsersPanel); ToggleUsersPanelCommand = ReactiveCommand.Create(ToggleUsersPanel);
} }
private void ToggleUsersPanel()
{
ShowOnlineUsers = !ShowOnlineUsers;
}
public void SetChannel(ChannelViewModel? channel, string serverUrl = "", bool isServerConnected = true) public void SetChannel(ChannelViewModel? channel, string serverUrl = "", bool isServerConnected = true)
{ {
if (channel is null) if (channel is null)
@@ -145,4 +140,9 @@ public sealed class ChatViewModel : ViewModelBase
OnlineUsers.Clear(); OnlineUsers.Clear();
OnlineUserCount = string.Empty; OnlineUserCount = string.Empty;
} }
private void ToggleUsersPanel()
{
ShowOnlineUsers = !ShowOnlineUsers;
}
} }
@@ -1060,4 +1060,3 @@ public sealed class MainWindowViewModel : ViewModelBase
return Chat.CurrentServerUrl; return Chat.CurrentServerUrl;
} }
} }
@@ -13,10 +13,9 @@ public sealed class MessageComposerViewModel : ViewModelBase
public event Action<string, string>? FileUploadRequested; public event Action<string, string>? FileUploadRequested;
private CommandHandler? _commandHandler;
private readonly ObservableCollection<UserViewModel> _onlineUsers = []; private readonly ObservableCollection<UserViewModel> _onlineUsers = [];
private readonly ObservableCollection<string> _channelNames = []; private readonly ObservableCollection<string> _channelNames = [];
private CommandHandler? _commandHandler;
public string Draft public string Draft
{ {
+7 -18
View File
@@ -5,13 +5,14 @@ using EchoHub.Core.Models;
namespace Decho.ViewModels; namespace Decho.ViewModels;
public sealed class UserViewModel : ViewModelBase public sealed class UserViewModel(UserPresenceDto dto) : ViewModelBase
{ {
public string Username { get; } private UserStatus _status = dto.Status;
public string DisplayName { get; } public string Username { get; } = dto.Username;
public string? NicknameColor { get; } public string DisplayName { get; } = dto.DisplayName ?? dto.Username;
public string? StatusMessage { get; } public string? NicknameColor { get; } = dto.NicknameColor;
public ServerRole Role { get; } public string? StatusMessage { get; } = dto.StatusMessage;
public ServerRole Role { get; } = dto.Role;
public UserStatus Status public UserStatus Status
{ {
@@ -30,8 +31,6 @@ public sealed class UserViewModel : ViewModelBase
} }
} }
private UserStatus _status;
public string FullName => DisplayName ?? Username; public string FullName => DisplayName ?? Username;
public IBrush? DisplayColor public IBrush? DisplayColor
@@ -71,14 +70,4 @@ public sealed class UserViewModel : ViewModelBase
UserStatus.Invisible => "Invisible", UserStatus.Invisible => "Invisible",
_ => "Offline", _ => "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;
}
} }
+1 -2
View File
@@ -16,6 +16,7 @@ namespace Decho.Views;
public partial class MessageItemView : UserControl public partial class MessageItemView : UserControl
{ {
private static readonly Regex MentionRegex = new(@"@(\w+)", RegexOptions.Compiled);
private CancellationTokenSource? _loadCts; private CancellationTokenSource? _loadCts;
private string? _loadedMessageId; private string? _loadedMessageId;
@@ -25,8 +26,6 @@ public partial class MessageItemView : UserControl
DataContextChanged += OnDataContextChanged; DataContextChanged += OnDataContextChanged;
} }
private static readonly Regex MentionRegex = new(@"@(\w+)", RegexOptions.Compiled);
private void OnDataContextChanged(object? sender, EventArgs args) private void OnDataContextChanged(object? sender, EventArgs args)
{ {
if (DataContext is MessageViewModel newMsg && newMsg.Model.Id == _loadedMessageId) if (DataContext is MessageViewModel newMsg && newMsg.Model.Id == _loadedMessageId)
+5 -5
View File
@@ -51,11 +51,6 @@ public sealed partial class ProfileWindow : Window
LastSeenText.Text = profile.LastSeenAt.ToString("g"); LastSeenText.Text = profile.LastSeenAt.ToString("g");
} }
private void OnCloseClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Close();
}
private static string FormatStatus(UserStatus status) private static string FormatStatus(UserStatus status)
{ {
return status switch return status switch
@@ -90,4 +85,9 @@ public sealed partial class ProfileWindow : Window
_ => role.ToString(), _ => role.ToString(),
}; };
} }
private void OnCloseClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Close();
}
} }