mirror of
https://github.com/Stone-Red-Code/Decho.git
synced 2026-09-09 07:46:14 +02:00
Add password support and UI for joining protected channels
This commit is contained in:
@@ -2,7 +2,7 @@ using System.Collections.ObjectModel;
|
|||||||
|
|
||||||
namespace Decho.Models;
|
namespace Decho.Models;
|
||||||
|
|
||||||
public sealed class ChannelModel(string id, string name, ObservableCollection<MessageModel> messages, string? topic = null, bool isPublic = true)
|
public sealed class ChannelModel(string id, string name, ObservableCollection<MessageModel> messages, string? topic = null, bool isPublic = true, bool isProtected = false)
|
||||||
{
|
{
|
||||||
public string Id { get; } = id;
|
public string Id { get; } = id;
|
||||||
|
|
||||||
@@ -12,5 +12,7 @@ public sealed class ChannelModel(string id, string name, ObservableCollection<Me
|
|||||||
|
|
||||||
public bool IsPublic { get; } = isPublic;
|
public bool IsPublic { get; } = isPublic;
|
||||||
|
|
||||||
|
public bool IsProtected { get; } = isProtected;
|
||||||
|
|
||||||
public ObservableCollection<MessageModel> Messages { get; } = messages;
|
public ObservableCollection<MessageModel> Messages { get; } = messages;
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ public sealed class ConnectionService : IDisposable
|
|||||||
await entry.Manager.SendMessageAsync(channelName, content);
|
await entry.Manager.SendMessageAsync(channelName, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MessageModel>> JoinChannelAsync(string serverUrl, string channelName)
|
public async Task<List<MessageModel>> JoinChannelAsync(string serverUrl, string channelName, string? password = null)
|
||||||
{
|
{
|
||||||
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
|
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
|
||||||
{
|
{
|
||||||
@@ -166,7 +166,7 @@ public sealed class ConnectionService : IDisposable
|
|||||||
|
|
||||||
if (entry.Manager.TrackChannel(channelName))
|
if (entry.Manager.TrackChannel(channelName))
|
||||||
{
|
{
|
||||||
var outcome = await entry.Manager.JoinChannelAsync(channelName);
|
var outcome = await entry.Manager.JoinChannelAsync(channelName, password);
|
||||||
return outcome.History.Select(m => MessageModelFromDto(m, entry)).ToList();
|
return outcome.History.Select(m => MessageModelFromDto(m, entry)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +480,8 @@ public sealed class ConnectionService : IDisposable
|
|||||||
dto.Name,
|
dto.Name,
|
||||||
[],
|
[],
|
||||||
dto.Topic,
|
dto.Topic,
|
||||||
dto.IsPublic);
|
dto.IsPublic,
|
||||||
|
dto.IsProtected);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static MessageModel MessageModelFromDto(MessageDto dto, ServerConnection entry)
|
internal static MessageModel MessageModelFromDto(MessageDto dto, ServerConnection entry)
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase
|
|||||||
|
|
||||||
public bool IsPublic => Model.IsPublic;
|
public bool IsPublic => Model.IsPublic;
|
||||||
|
|
||||||
|
public bool IsProtected => Model.IsProtected;
|
||||||
|
|
||||||
|
public bool IsLocked
|
||||||
|
{
|
||||||
|
get => field;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref field, value);
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableCollection<MessageViewModel> Messages { get; } = new ObservableCollection<MessageViewModel>(
|
public ObservableCollection<MessageViewModel> Messages { get; } = new ObservableCollection<MessageViewModel>(
|
||||||
model.Messages.Select(message => new MessageViewModel(message)));
|
model.Messages.Select(message => new MessageViewModel(message)));
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using EchoHub.Core.DTOs;
|
|||||||
using EchoHub.Core.Models;
|
using EchoHub.Core.Models;
|
||||||
|
|
||||||
using MsBox.Avalonia;
|
using MsBox.Avalonia;
|
||||||
|
using MsBox.Avalonia.Base;
|
||||||
using MsBox.Avalonia.Enums;
|
using MsBox.Avalonia.Enums;
|
||||||
|
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
@@ -86,11 +87,11 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var box = MessageBoxManager.GetMessageBoxStandard(
|
IMsBox<ButtonResult> box = MessageBoxManager.GetMessageBoxStandard(
|
||||||
"Connection Failed",
|
"Connection Failed",
|
||||||
$"Could not connect to server:\n{ex.Message}",
|
$"Could not connect to server:\n{ex.Message}",
|
||||||
ButtonEnum.Ok);
|
ButtonEnum.Ok);
|
||||||
await box.ShowWindowDialogAsync(_mainWindow);
|
_ = await box.ShowWindowDialogAsync(_mainWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +137,57 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
return await dialog.ShowDialog<ConnectDialogResult?>(_mainWindow);
|
return await dialog.ShowDialog<ConnectDialogResult?>(_mainWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<string?> ShowPasswordPromptWindowAsync()
|
||||||
|
{
|
||||||
|
var window = new Avalonia.Controls.Window
|
||||||
|
{
|
||||||
|
Title = "Channel Password",
|
||||||
|
Width = 320,
|
||||||
|
Height = 180,
|
||||||
|
WindowStartupLocation = Avalonia.Controls.WindowStartupLocation.CenterOwner,
|
||||||
|
SizeToContent = Avalonia.Controls.SizeToContent.Height,
|
||||||
|
CanResize = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
var passwordBox = new Avalonia.Controls.TextBox { Watermark = "Password", PasswordChar = '*' };
|
||||||
|
string? result = null;
|
||||||
|
|
||||||
|
var joinBtn = new Avalonia.Controls.Button { Content = "Join", IsDefault = true };
|
||||||
|
var cancelBtn = new Avalonia.Controls.Button { Content = "Cancel", IsCancel = true };
|
||||||
|
|
||||||
|
joinBtn.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
result = passwordBox.Text;
|
||||||
|
window.Close();
|
||||||
|
};
|
||||||
|
cancelBtn.Click += (_, _) => window.Close();
|
||||||
|
|
||||||
|
var buttons = new Avalonia.Controls.StackPanel
|
||||||
|
{
|
||||||
|
Orientation = Avalonia.Layout.Orientation.Horizontal,
|
||||||
|
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
|
||||||
|
Spacing = 8,
|
||||||
|
Children = { cancelBtn, joinBtn },
|
||||||
|
};
|
||||||
|
|
||||||
|
var panel = new Avalonia.Controls.StackPanel
|
||||||
|
{
|
||||||
|
Margin = new Avalonia.Thickness(12),
|
||||||
|
Spacing = 8,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Avalonia.Controls.TextBlock { Text = "This channel is password protected." },
|
||||||
|
new Avalonia.Controls.TextBlock { Text = "Enter the channel password:" },
|
||||||
|
passwordBox,
|
||||||
|
buttons,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.Content = panel;
|
||||||
|
await window.ShowDialog(_mainWindow!);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private void WireCommandHandlerEvents()
|
private void WireCommandHandlerEvents()
|
||||||
{
|
{
|
||||||
_commandHandler.OnSetStatus += async (status, message) =>
|
_commandHandler.OnSetStatus += async (status, message) =>
|
||||||
@@ -162,7 +214,7 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MessageModel> channel = await ConnectionService.JoinChannelAsync(serverUrl, channelName);
|
List<MessageModel> channel = await ConnectionService.JoinChannelAsync(serverUrl, channelName, password);
|
||||||
EnsureChannelInList(serverUrl, channelName);
|
EnsureChannelInList(serverUrl, channelName);
|
||||||
ChannelModel? channelModel = FindChannel(serverUrl, channelName);
|
ChannelModel? channelModel = FindChannel(serverUrl, channelName);
|
||||||
if (channelModel is not null)
|
if (channelModel is not null)
|
||||||
@@ -456,10 +508,11 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
Sidebar.Servers.Insert(insertIndex, serverVm);
|
Sidebar.Servers.Insert(insertIndex, serverVm);
|
||||||
if (serverVm.Channels.Count > 0)
|
|
||||||
{
|
ChannelViewModel? defaultChannel = serverVm.Channels.FirstOrDefault(c => c.Name == HubConstants.DefaultChannel);
|
||||||
serverVm.SelectedChannel = serverVm.Channels[0];
|
defaultChannel ??= serverVm.Channels.FirstOrDefault(c => c.IsPublic);
|
||||||
}
|
defaultChannel ??= serverVm.Channels.FirstOrDefault();
|
||||||
|
serverVm.SelectedChannel = defaultChannel;
|
||||||
|
|
||||||
StatusText = $"Connected to {server.Name}";
|
StatusText = $"Connected to {server.Name}";
|
||||||
});
|
});
|
||||||
@@ -601,14 +654,26 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
Chat.Composer.SetCommandHandler(new CommandHandler());
|
Chat.Composer.SetCommandHandler(new CommandHandler());
|
||||||
|
|
||||||
|
if (channel.IsProtected && channel.Messages.Count == 0)
|
||||||
|
{
|
||||||
|
channel.IsLocked = true;
|
||||||
|
Chat.Composer.IsConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
string? password = null;
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
List<MessageModel> history = await ConnectionService.JoinChannelAsync(serverUrl, channel.Name);
|
try
|
||||||
|
{
|
||||||
|
List<MessageModel> history = await ConnectionService.JoinChannelAsync(serverUrl, channel.Name, password);
|
||||||
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
|
channel.IsLocked = false;
|
||||||
|
Chat.Composer.IsConnected = isServerConnected;
|
||||||
|
|
||||||
if (channel.Messages.Count == 0)
|
if (channel.Messages.Count == 0)
|
||||||
{
|
{
|
||||||
foreach (MessageModel msg in history)
|
foreach (MessageModel msg in history)
|
||||||
@@ -617,10 +682,32 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
catch
|
return;
|
||||||
{
|
}
|
||||||
// Channel might not be available
|
catch (EchoHub.Client.Services.ChannelPasswordRequiredException)
|
||||||
|
{
|
||||||
|
string? pwd = await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
|
{
|
||||||
|
if (_mainWindow is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await ShowPasswordPromptWindowAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(pwd))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
password = pwd;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -697,11 +784,11 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
serverVm.IsConnecting = false;
|
serverVm.IsConnecting = false;
|
||||||
var box = MessageBoxManager.GetMessageBoxStandard(
|
IMsBox<ButtonResult> box = MessageBoxManager.GetMessageBoxStandard(
|
||||||
"Connection Failed",
|
"Connection Failed",
|
||||||
$"Could not connect to server:\n{ex.Message}",
|
$"Could not connect to server:\n{ex.Message}",
|
||||||
ButtonEnum.Ok);
|
ButtonEnum.Ok);
|
||||||
await box.ShowWindowDialogAsync(_mainWindow);
|
_ = await box.ShowWindowDialogAsync(_mainWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user