Patch for EchoHub v0.2.17

This commit is contained in:
Stone_Red
2026-07-18 19:46:50 +02:00
parent 4a03fba266
commit a1f3f6732c
11 changed files with 124 additions and 24 deletions
+5 -1
View File
@@ -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, bool isProtected = false) public sealed class ChannelModel(string id, string name, ObservableCollection<MessageModel> messages, string? topic = null, bool isPublic = true, bool isProtected = false, bool isEncrypted = false, bool isSystem = false)
{ {
public string Id { get; } = id; public string Id { get; } = id;
@@ -14,5 +14,9 @@ public sealed class ChannelModel(string id, string name, ObservableCollection<Me
public bool IsProtected { get; } = isProtected; public bool IsProtected { get; } = isProtected;
public bool IsEncrypted { get; } = isEncrypted;
public bool IsSystem { get; } = isSystem;
public ObservableCollection<MessageModel> Messages { get; } = messages; public ObservableCollection<MessageModel> Messages { get; } = messages;
} }
+3 -1
View File
@@ -205,7 +205,9 @@ internal sealed class ChannelService : IChannelService
[], [],
channelDto.Topic, channelDto.Topic,
channelDto.IsPublic, channelDto.IsPublic,
channelDto.IsProtected); channelDto.IsProtected,
channelDto.IsEncrypted,
channelDto.IsSystem);
entry.Server.Channels.Add(channel); entry.Server.Channels.Add(channel);
} }
+39 -4
View File
@@ -1,3 +1,6 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Decho.Models; using Decho.Models;
using EchoHub.Client.Config; using EchoHub.Client.Config;
@@ -5,8 +8,10 @@ using EchoHub.Client.Services;
using EchoHub.Client.UI.Dialogs; using EchoHub.Client.UI.Dialogs;
using EchoHub.Core.Constants; using EchoHub.Core.Constants;
using EchoHub.Core.DTOs; using EchoHub.Core.DTOs;
using EchoHub.Core.Models;
using EchoHub.Core.Services; using MsBox.Avalonia;
using MsBox.Avalonia.Base;
using MsBox.Avalonia.Enums;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
@@ -140,7 +145,9 @@ public sealed class ConnectionService : IConnectionService
[], [],
dto.Topic, dto.Topic,
dto.IsPublic, dto.IsPublic,
dto.IsProtected); dto.IsProtected,
dto.IsEncrypted,
dto.IsSystem);
} }
internal static MessageModel MessageModelFromDto(MessageDto dto, ServerConnection entry) internal static MessageModel MessageModelFromDto(MessageDto dto, ServerConnection entry)
@@ -219,11 +226,37 @@ public sealed class ConnectionService : IConnectionService
SaveRefreshToken(dialogResult.ServerUrl, dialogResult.RememberMe); SaveRefreshToken(dialogResult.ServerUrl, dialogResult.RememberMe);
ServerAdded?.Invoke(serverModel); ServerAdded?.Invoke(serverModel);
// Check for version mismatch
//if (result.ServerInfo is not null && !string.IsNullOrEmpty(result.ServerInfo.Version)
// && result.ServerInfo.Version != App.AppVersion)
//{
// await ShowVersionMismatchDialogAsync(dialogResult.ServerUrl, result.ServerInfo.Version);
//}
AutoJoinRemainingChannels(dialogResult.ServerUrl, result.Channels); AutoJoinRemainingChannels(dialogResult.ServerUrl, result.Channels);
return serverModel; return serverModel;
} }
//private async Task ShowVersionMismatchDialogAsync(string serverUrl, string serverVersion)
//{
// await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () =>
// {
// IMsBox<ButtonResult> box = MessageBoxManager.GetMessageBoxStandard(
// "Version Mismatch",
// $"Server version {serverVersion} does not match client version {App.AppVersion}.\n\nSome features may not work correctly. Consider updating both to the same version.",
// ButtonEnum.YesNo);
//
// ButtonResult result = await box.ShowWindowDialogAsync(
// (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow);
//
// if (result == ButtonResult.No)
// {
// _ = DisconnectAsync(serverUrl);
// }
// });
//}
private async void AutoJoinRemainingChannels(string serverUrl, List<ChannelDto> channels) private async void AutoJoinRemainingChannels(string serverUrl, List<ChannelDto> channels)
{ {
ClientConfig config = ConfigManager.Load(); ClientConfig config = ConfigManager.Load();
@@ -342,7 +375,9 @@ public sealed class ConnectionService : IConnectionService
[], [],
channel.Topic, channel.Topic,
channel.IsPublic, channel.IsPublic,
channel.IsProtected); channel.IsProtected,
channel.IsEncrypted,
channel.IsSystem);
entry.Server.Channels.Add(model); entry.Server.Channels.Add(model);
ChannelAdded?.Invoke(entry.Server.ServerUrl, model); ChannelAdded?.Invoke(entry.Server.ServerUrl, model);
} }
+12
View File
@@ -32,6 +32,18 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase
public bool IsNotProtected => !Model.IsProtected; public bool IsNotProtected => !Model.IsProtected;
public bool IsSystem => Model.IsSystem;
public bool IsNotSystem => !Model.IsSystem;
public bool ShowLockIcon => Model.IsProtected && IsLocked;
public bool ShowUnlockIcon => Model.IsProtected && !IsLocked;
public bool ShowServerIcon => Model.IsSystem && !Model.IsProtected;
public bool ShowHashtagIcon => !Model.IsProtected && !Model.IsSystem;
public bool IsLocked public bool IsLocked
{ {
get; get;
+23 -6
View File
@@ -892,7 +892,7 @@ public sealed class MainWindowViewModel : ViewModelBase
ChannelJoinResult joinResult = await ChannelService.JoinChannelAsync(server.ServerUrl, channel.Name); ChannelJoinResult joinResult = await ChannelService.JoinChannelAsync(server.ServerUrl, channel.Name);
ChannelModel channelModel = new ChannelModel( ChannelModel channelModel = new ChannelModel(
channel.Id.ToString(), channel.Name, [], channel.Topic, channel.IsPublic, channel.IsProtected); channel.Id.ToString(), channel.Name, [], channel.Topic, channel.IsPublic, channel.IsProtected, channel.IsEncrypted, channel.IsSystem);
ServerViewModel? serverVm = Sidebar.GetServer(server.ServerUrl); ServerViewModel? serverVm = Sidebar.GetServer(server.ServerUrl);
if (serverVm is not null) if (serverVm is not null)
@@ -1040,10 +1040,18 @@ public sealed class MainWindowViewModel : ViewModelBase
{ {
Chat.Composer.SetCommandHandler(new CommandHandler()); Chat.Composer.SetCommandHandler(new CommandHandler());
if (channel.IsProtected && channel.Messages.Count == 0) if (channel.IsSystem)
{
Chat.Composer.IsReadOnly = true;
}
else if (channel.IsProtected && channel.Messages.Count == 0)
{ {
channel.IsLocked = true; channel.IsLocked = true;
Chat.Composer.IsConnected = false; Chat.Composer.IsReadOnly = true;
}
else
{
Chat.Composer.IsReadOnly = false;
} }
_ = Task.Run(async () => _ = Task.Run(async () =>
@@ -1064,7 +1072,7 @@ public sealed class MainWindowViewModel : ViewModelBase
Avalonia.Threading.Dispatcher.UIThread.Post(() => Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{ {
channel.IsLocked = true; channel.IsLocked = true;
Chat.Composer.IsConnected = false; Chat.Composer.IsReadOnly = true;
}); });
break; break;
} }
@@ -1078,6 +1086,11 @@ public sealed class MainWindowViewModel : ViewModelBase
channel.IsLocked = false; channel.IsLocked = false;
Chat.Composer.IsConnected = isServerConnected; Chat.Composer.IsConnected = isServerConnected;
if (channel.IsProtected)
{
Chat.Composer.IsReadOnly = false;
}
if (!channel.Messages.Any(m => m.AuthorName != "System")) if (!channel.Messages.Any(m => m.AuthorName != "System"))
{ {
foreach (MessageModel msg in joinResult.History) foreach (MessageModel msg in joinResult.History)
@@ -1090,7 +1103,7 @@ public sealed class MainWindowViewModel : ViewModelBase
_ = RefreshOnlineUsersAsync(serverUrl, channel.Name); _ = RefreshOnlineUsersAsync(serverUrl, channel.Name);
return; return;
} }
catch (EchoHub.Client.Services.ChannelPasswordRequiredException) catch (ChannelPasswordRequiredException)
{ {
string? pwd = await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () => string? pwd = await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () =>
{ {
@@ -1242,7 +1255,11 @@ public sealed class MainWindowViewModel : ViewModelBase
ChannelModel channelModel = new ChannelModel( ChannelModel channelModel = new ChannelModel(
Guid.NewGuid().ToString("N"), Guid.NewGuid().ToString("N"),
channelName, channelName,
[]); [],
isPublic: true,
isProtected: false,
isEncrypted: false,
isSystem: false);
ChannelViewModel channelVm = new ChannelViewModel(channelModel); ChannelViewModel channelVm = new ChannelViewModel(channelModel);
serverVm.Channels.Add(channelVm); serverVm.Channels.Add(channelVm);
} }
@@ -39,9 +39,25 @@ public sealed class MessageComposerViewModel : ViewModelBase
public bool IsConnected public bool IsConnected
{ {
get; get;
set => this.RaiseAndSetIfChanged(ref field, value); set
{
this.RaiseAndSetIfChanged(ref field, value);
this.RaisePropertyChanged(nameof(IsEditable));
}
} = true; } = true;
public bool IsReadOnly
{
get;
set
{
this.RaiseAndSetIfChanged(ref field, value);
this.RaisePropertyChanged(nameof(IsEditable));
}
} = false;
public bool IsEditable => !IsReadOnly && IsConnected;
public ReactiveCommand<Unit, Unit> SendCommand { get; } public ReactiveCommand<Unit, Unit> SendCommand { get; }
public bool HasCommandHandler => _commandHandler is not null; public bool HasCommandHandler => _commandHandler is not null;
@@ -84,10 +100,11 @@ public sealed class MessageComposerViewModel : ViewModelBase
Autocomplete = new AutocompleteController([mentionProvider, channelProvider]); Autocomplete = new AutocompleteController([mentionProvider, channelProvider]);
IObservable<bool> canSend = this.WhenAnyValue( IObservable<bool> canSend = this.WhenAnyValue(
x => x.Draft, x => x.Draft,
x => x.HasStagedFiles, x => x.HasStagedFiles,
(draft, hasFiles) => x => x.IsReadOnly,
!string.IsNullOrWhiteSpace(draft) || hasFiles); (draft, hasFiles, isReadOnly) =>
(!string.IsNullOrWhiteSpace(draft) || hasFiles) && !isReadOnly);
SendCommand = ReactiveCommand.Create(Send, canSend); SendCommand = ReactiveCommand.Create(Send, canSend);
_ = this.WhenAnyValue(x => x.Draft).Subscribe(OnDraftChanged); _ = this.WhenAnyValue(x => x.Draft).Subscribe(OnDraftChanged);
+4 -2
View File
@@ -14,8 +14,10 @@
<DataTemplate x:DataType="vm:ChannelViewModel"> <DataTemplate x:DataType="vm:ChannelViewModel">
<Grid ColumnDefinitions="Auto, *, Auto" Margin="2 0"> <Grid ColumnDefinitions="Auto, *, Auto" Margin="2 0">
<Grid Grid.Column="0" Margin="0 0 4 0"> <Grid Grid.Column="0" Margin="0 0 4 0">
<i:Icon Value="fa-hashtag" FontWeight="Bold" VerticalAlignment="Center" IsVisible="{Binding IsNotProtected}" /> <i:Icon Value="fa-lock" FontWeight="Bold" VerticalAlignment="Center" IsVisible="{Binding ShowLockIcon}" />
<i:Icon Value="fa-lock" FontWeight="Bold" VerticalAlignment="Center" IsVisible="{Binding IsProtected}" /> <i:Icon Value="fa-lock-open" FontWeight="Bold" VerticalAlignment="Center" IsVisible="{Binding ShowUnlockIcon}" />
<i:Icon Value="fa-server" FontWeight="Bold" VerticalAlignment="Center" IsVisible="{Binding ShowServerIcon}" />
<i:Icon Value="fa-hashtag" FontWeight="Bold" VerticalAlignment="Center" IsVisible="{Binding ShowHashtagIcon}" />
</Grid> </Grid>
<TextBlock Grid.Column="1" Text="{Binding Name}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" /> <TextBlock Grid.Column="1" Text="{Binding Name}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<Border Grid.Column="2" <Border Grid.Column="2"
@@ -25,6 +25,12 @@
<CheckBox x:Name="RememberCheck" Content="Remember me" IsChecked="True" /> <CheckBox x:Name="RememberCheck" Content="Remember me" IsChecked="True" />
<TextBlock Text="Display Name (optional):" FontWeight="SemiBold" />
<TextBox x:Name="DisplayNameBox" Watermark="Display name" />
<TextBlock Text="Invite Code (optional):" FontWeight="SemiBold" />
<TextBox x:Name="InviteCodeBox" Watermark="Invite code" />
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Spacing="10" Spacing="10"
+5 -1
View File
@@ -100,6 +100,8 @@ public sealed partial class ConnectDialogWindow : Window
string url = UrlBox.Text?.Trim() ?? ""; string url = UrlBox.Text?.Trim() ?? "";
string user = UserBox.Text?.Trim() ?? ""; string user = UserBox.Text?.Trim() ?? "";
string pass = PassBox.Text ?? ""; string pass = PassBox.Text ?? "";
string displayName = DisplayNameBox.Text?.Trim() ?? "";
string inviteCode = InviteCodeBox.Text?.Trim() ?? "";
if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(user) || string.IsNullOrEmpty(pass)) if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(user) || string.IsNullOrEmpty(pass))
{ {
@@ -108,7 +110,9 @@ public sealed partial class ConnectDialogWindow : Window
return; return;
} }
Close(new ConnectDialogResult(url, user, pass, true, RememberCheck.IsChecked ?? false, null)); Close(new ConnectDialogResult(url, user, pass, true, RememberCheck.IsChecked ?? false, null,
string.IsNullOrEmpty(displayName) ? null : displayName,
string.IsNullOrEmpty(inviteCode) ? null : inviteCode));
} }
private void OnCancelClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) private void OnCancelClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+4 -3
View File
@@ -3,6 +3,7 @@
xmlns:vm="using:Decho.ViewModels" xmlns:vm="using:Decho.ViewModels"
xmlns:models="using:Decho.Models" xmlns:models="using:Decho.Models"
xmlns:i="https://github.com/projektanker/icons.avalonia" xmlns:i="https://github.com/projektanker/icons.avalonia"
xmlns:converters="using:Avalonia.Data.Converters"
x:Class="Decho.Views.MessageComposerView" x:Class="Decho.Views.MessageComposerView"
x:DataType="vm:MessageComposerViewModel"> x:DataType="vm:MessageComposerViewModel">
<Grid RowDefinitions="Auto,Auto,*"> <Grid RowDefinitions="Auto,Auto,*">
@@ -78,7 +79,7 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="0 0 5 0" Margin="0 0 5 0"
Text="{Binding Draft, Mode=TwoWay}" Text="{Binding Draft, Mode=TwoWay}"
IsEnabled="{Binding IsConnected}" IsEnabled="{Binding IsEditable}"
AcceptsReturn="False" AcceptsReturn="False"
TextWrapping="Wrap" TextWrapping="Wrap"
KeyDown="OnTextBoxKeyDown" /> KeyDown="OnTextBoxKeyDown" />
@@ -119,7 +120,7 @@
VerticalAlignment="Top" VerticalAlignment="Top"
i:Attached.Icon="fa-paper-plane" i:Attached.Icon="fa-paper-plane"
Command="{Binding SendCommand}" Command="{Binding SendCommand}"
IsEnabled="{Binding IsConnected}" /> IsEnabled="{Binding IsEditable}" />
<Button Grid.Column="2" <Button Grid.Column="2"
Height="33" Height="33"
Width="33" Width="33"
@@ -127,7 +128,7 @@
Click="OnFileUploadClicked" Click="OnFileUploadClicked"
VerticalAlignment="Top" VerticalAlignment="Top"
i:Attached.Icon="fa-paperclip" i:Attached.Icon="fa-paperclip"
IsEnabled="{Binding IsConnected}" /> IsEnabled="{Binding IsEditable}" />
</Grid> </Grid>
</Grid> </Grid>
</UserControl> </UserControl>