diff --git a/src/PostIt/PostIt/Models/ChatMessage.cs b/src/PostIt/PostIt/Models/ChatMessage.cs new file mode 100644 index 000000000..52733311a --- /dev/null +++ b/src/PostIt/PostIt/Models/ChatMessage.cs @@ -0,0 +1,15 @@ +namespace PostIt.ViewModels.Chat +{ + /// + /// DTO de message de salon, calqué sur la charge envoyée par + /// ChatHub.Send côté serveur ({ Name, Room, Message }). + /// Le protocole JSON de SignalR sérialise en camelCase ; la + /// désérialisation est insensible à la casse par défaut. + /// + public class ChatMessage + { + public string? Name { get; set; } + public string? Room { get; set; } + public string? Message { get; set; } + } +} diff --git a/src/PostIt/PostIt/ViewModels/Chat/ChatViewModel.cs b/src/PostIt/PostIt/ViewModels/Chat/ChatViewModel.cs index 54872991e..7cbdfe62f 100644 --- a/src/PostIt/PostIt/ViewModels/Chat/ChatViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/Chat/ChatViewModel.cs @@ -45,8 +45,17 @@ namespace PostIt.ViewModels.Chat public partial StatusNotice Status { get; set; } [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(SendMessageCommand))] public partial bool IsConnected { get; set; } = false; + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(SendMessageCommand))] + public partial bool HasJoinedRoom { get; set; } = false; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(SendMessageCommand))] + public partial string MessageToSend { get; set; } = ""; + // Listes dynamiques pour l'UI public ObservableCollection Messages { get; } = new(); public ObservableCollection ConnectedUsers { get; } = new(); @@ -58,6 +67,28 @@ namespace PostIt.ViewModels.Chat [RelayCommand] public async Task JoinRoomCommand() => await JoinRoomAsync(); + // Le serveur (ChatHub.Send) refuse tout message d'un utilisateur + // qui n'a pas joint le salon ("NoJoinNoSend") : on garde la + // commande inactive tant que le Join n'a pas abouti. + private bool CanSendMessage() => + IsConnected && HasJoinedRoom && !string.IsNullOrWhiteSpace(MessageToSend); + + [RelayCommand(CanExecute = nameof(CanSendMessage))] + private async Task SendMessageAsync() + { + if (_connection == null) return; + var text = MessageToSend.Trim(); + try + { + await _connection.InvokeAsync("Send", CurrentRoom, text); + MessageToSend = string.Empty; + } + catch (Exception ex) + { + Messages.Add($"Erreur d'envoi : {ex.Message}"); + } + } + public override bool CanNavigateNext { get; protected set; } = false; public override bool CanNavigatePrevious { get; protected set; } = true; @@ -70,6 +101,7 @@ namespace PostIt.ViewModels.Chat _connection = null; } IsConnected = false; + HasJoinedRoom = false; Status = new StatusNotice("Déconnecté", StatusSeverity.Info, new RelayCheckCommand("Connecter", async () => await ConnectAsync())); } @@ -140,11 +172,19 @@ namespace PostIt.ViewModels.Chat Avalonia.Threading.Dispatcher.UIThread.Post(() => { Messages.Add($"Salon rejoint avec succès : {roomInfo.Name}"); + HasJoinedRoom = true; ConnectedUsers.Clear(); foreach (var u in roomInfo.Users) ConnectedUsers.Add(u); }); }); + // 4. Réception des messages du salon (diffusés par ChatHub.Send) + _connection.On("ReceiveMessage", (msg) => + { + Avalonia.Threading.Dispatcher.UIThread.Post(() => + Messages.Add($"[{msg.Room}] {msg.Name} : {msg.Message}")); + }); + await _connection.StartAsync(); IsConnected = true; Status = new StatusNotice("Connecté au Hub SignalR", StatusSeverity.Info, new RelayCheckCommand("Déconnecter", async () => await DisconnectAsync())); diff --git a/src/PostIt/PostIt/Views/Chat/ChatPage.axaml b/src/PostIt/PostIt/Views/Chat/ChatPage.axaml index 55251f544..415b989b1 100644 --- a/src/PostIt/PostIt/Views/Chat/ChatPage.axaml +++ b/src/PostIt/PostIt/Views/Chat/ChatPage.axaml @@ -16,7 +16,7 @@ - + + + + + + + + +