diff --git a/src/Decho/Models/ChannelModel.cs b/src/Decho/Models/ChannelModel.cs index 3e37b92..112e127 100644 --- a/src/Decho/Models/ChannelModel.cs +++ b/src/Decho/Models/ChannelModel.cs @@ -2,7 +2,7 @@ using System.Collections.ObjectModel; namespace Decho.Models; -public sealed class ChannelModel(string id, string name, ObservableCollection messages, string? topic = null, bool isPublic = true, bool isProtected = false) +public sealed class ChannelModel(string id, string name, ObservableCollection messages, string? topic = null, bool isPublic = true, bool isProtected = false, bool isEncrypted = false, bool isSystem = false) { public string Id { get; } = id; @@ -14,5 +14,9 @@ public sealed class ChannelModel(string id, string name, ObservableCollection Messages { get; } = messages; } \ No newline at end of file diff --git a/src/Decho/Services/ChannelService.cs b/src/Decho/Services/ChannelService.cs index 9e2b5a5..14f9d7e 100644 --- a/src/Decho/Services/ChannelService.cs +++ b/src/Decho/Services/ChannelService.cs @@ -205,7 +205,9 @@ internal sealed class ChannelService : IChannelService [], channelDto.Topic, channelDto.IsPublic, - channelDto.IsProtected); + channelDto.IsProtected, + channelDto.IsEncrypted, + channelDto.IsSystem); entry.Server.Channels.Add(channel); } diff --git a/src/Decho/Services/ConnectionService.cs b/src/Decho/Services/ConnectionService.cs index 35c8993..d5f9732 100644 --- a/src/Decho/Services/ConnectionService.cs +++ b/src/Decho/Services/ConnectionService.cs @@ -1,3 +1,6 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; + using Decho.Models; using EchoHub.Client.Config; @@ -5,8 +8,10 @@ using EchoHub.Client.Services; using EchoHub.Client.UI.Dialogs; using EchoHub.Core.Constants; 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.Diagnostics; @@ -140,7 +145,9 @@ public sealed class ConnectionService : IConnectionService [], dto.Topic, dto.IsPublic, - dto.IsProtected); + dto.IsProtected, + dto.IsEncrypted, + dto.IsSystem); } internal static MessageModel MessageModelFromDto(MessageDto dto, ServerConnection entry) @@ -219,11 +226,37 @@ public sealed class ConnectionService : IConnectionService SaveRefreshToken(dialogResult.ServerUrl, dialogResult.RememberMe); 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); return serverModel; } + //private async Task ShowVersionMismatchDialogAsync(string serverUrl, string serverVersion) + //{ + // await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () => + // { + // IMsBox 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 channels) { ClientConfig config = ConfigManager.Load(); @@ -342,7 +375,9 @@ public sealed class ConnectionService : IConnectionService [], channel.Topic, channel.IsPublic, - channel.IsProtected); + channel.IsProtected, + channel.IsEncrypted, + channel.IsSystem); entry.Server.Channels.Add(model); ChannelAdded?.Invoke(entry.Server.ServerUrl, model); } diff --git a/src/Decho/ViewModels/ChannelViewModel.cs b/src/Decho/ViewModels/ChannelViewModel.cs index cc94a56..64600c7 100644 --- a/src/Decho/ViewModels/ChannelViewModel.cs +++ b/src/Decho/ViewModels/ChannelViewModel.cs @@ -32,6 +32,18 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase 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 { get; diff --git a/src/Decho/ViewModels/MainWindowViewModel.cs b/src/Decho/ViewModels/MainWindowViewModel.cs index 8878abd..cf97eac 100644 --- a/src/Decho/ViewModels/MainWindowViewModel.cs +++ b/src/Decho/ViewModels/MainWindowViewModel.cs @@ -892,7 +892,7 @@ public sealed class MainWindowViewModel : ViewModelBase ChannelJoinResult joinResult = await ChannelService.JoinChannelAsync(server.ServerUrl, channel.Name); 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); if (serverVm is not null) @@ -1040,10 +1040,18 @@ public sealed class MainWindowViewModel : ViewModelBase { 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; - Chat.Composer.IsConnected = false; + Chat.Composer.IsReadOnly = true; + } + else + { + Chat.Composer.IsReadOnly = false; } _ = Task.Run(async () => @@ -1064,7 +1072,7 @@ public sealed class MainWindowViewModel : ViewModelBase Avalonia.Threading.Dispatcher.UIThread.Post(() => { channel.IsLocked = true; - Chat.Composer.IsConnected = false; + Chat.Composer.IsReadOnly = true; }); break; } @@ -1078,6 +1086,11 @@ public sealed class MainWindowViewModel : ViewModelBase channel.IsLocked = false; Chat.Composer.IsConnected = isServerConnected; + if (channel.IsProtected) + { + Chat.Composer.IsReadOnly = false; + } + if (!channel.Messages.Any(m => m.AuthorName != "System")) { foreach (MessageModel msg in joinResult.History) @@ -1090,7 +1103,7 @@ public sealed class MainWindowViewModel : ViewModelBase _ = RefreshOnlineUsersAsync(serverUrl, channel.Name); return; } - catch (EchoHub.Client.Services.ChannelPasswordRequiredException) + catch (ChannelPasswordRequiredException) { string? pwd = await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () => { @@ -1242,7 +1255,11 @@ public sealed class MainWindowViewModel : ViewModelBase ChannelModel channelModel = new ChannelModel( Guid.NewGuid().ToString("N"), channelName, - []); + [], + isPublic: true, + isProtected: false, + isEncrypted: false, + isSystem: false); ChannelViewModel channelVm = new ChannelViewModel(channelModel); serverVm.Channels.Add(channelVm); } diff --git a/src/Decho/ViewModels/MessageComposerViewModel.cs b/src/Decho/ViewModels/MessageComposerViewModel.cs index 56ed598..78ae5e9 100644 --- a/src/Decho/ViewModels/MessageComposerViewModel.cs +++ b/src/Decho/ViewModels/MessageComposerViewModel.cs @@ -39,9 +39,25 @@ public sealed class MessageComposerViewModel : ViewModelBase public bool IsConnected { get; - set => this.RaiseAndSetIfChanged(ref field, value); + set + { + this.RaiseAndSetIfChanged(ref field, value); + this.RaisePropertyChanged(nameof(IsEditable)); + } } = true; + public bool IsReadOnly + { + get; + set + { + this.RaiseAndSetIfChanged(ref field, value); + this.RaisePropertyChanged(nameof(IsEditable)); + } + } = false; + + public bool IsEditable => !IsReadOnly && IsConnected; + public ReactiveCommand SendCommand { get; } public bool HasCommandHandler => _commandHandler is not null; @@ -84,10 +100,11 @@ public sealed class MessageComposerViewModel : ViewModelBase Autocomplete = new AutocompleteController([mentionProvider, channelProvider]); IObservable canSend = this.WhenAnyValue( - x => x.Draft, - x => x.HasStagedFiles, - (draft, hasFiles) => - !string.IsNullOrWhiteSpace(draft) || hasFiles); + x => x.Draft, + x => x.HasStagedFiles, + x => x.IsReadOnly, + (draft, hasFiles, isReadOnly) => + (!string.IsNullOrWhiteSpace(draft) || hasFiles) && !isReadOnly); SendCommand = ReactiveCommand.Create(Send, canSend); _ = this.WhenAnyValue(x => x.Draft).Subscribe(OnDraftChanged); diff --git a/src/Decho/Views/ChannelListView.axaml b/src/Decho/Views/ChannelListView.axaml index 4d83074..b434d13 100644 --- a/src/Decho/Views/ChannelListView.axaml +++ b/src/Decho/Views/ChannelListView.axaml @@ -14,8 +14,10 @@ - - + + + + + + + + + + @@ -78,7 +79,7 @@ HorizontalAlignment="Stretch" Margin="0 0 5 0" Text="{Binding Draft, Mode=TwoWay}" - IsEnabled="{Binding IsConnected}" + IsEnabled="{Binding IsEditable}" AcceptsReturn="False" TextWrapping="Wrap" KeyDown="OnTextBoxKeyDown" /> @@ -119,7 +120,7 @@ VerticalAlignment="Top" i:Attached.Icon="fa-paper-plane" Command="{Binding SendCommand}" - IsEnabled="{Binding IsConnected}" /> + IsEnabled="{Binding IsEditable}" />