Skip to content

Commit c48a20e

Browse files
committed
functional signalr chat \o/
1 parent 4abecde commit c48a20e

File tree

8 files changed

+145
-53
lines changed

8 files changed

+145
-53
lines changed

src/web/Client/Features/Chat/ChatClientHub.razor

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Jordnaer.Client.SignalR;
2+
using Jordnaer.Shared;
3+
using Microsoft.AspNetCore.Components;
4+
using Microsoft.AspNetCore.SignalR.Client;
5+
6+
namespace Jordnaer.Client.Features.Chat;
7+
8+
public class ChatSignalRClient : SignalRClientBase
9+
{
10+
public ChatSignalRClient(NavigationManager navigationManager) : base(navigationManager, "/hubs/chat") { }
11+
12+
public void OnMessageReceived(Func<ChatMessageDto, Task> action)
13+
{
14+
if (!Started && HubConnection is not null)
15+
HubConnection.On(nameof(IChatHub.ReceiveChatMessage), action);
16+
}
17+
18+
public void OnChatStarted(Action<StartChat> action)
19+
{
20+
if (!Started && HubConnection is not null)
21+
HubConnection.On(nameof(IChatHub.StartChat), action);
22+
}
23+
}

src/web/Client/Features/Chat/LargeChatComponent.razor

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@inject IProfileCache ProfileCache
33
@inject ISnackbar Snackbar
44
@inject IJSRuntime JsRuntime
5+
@inject ChatSignalRClient ChatSignalRClient
56
@attribute [Authorize]
67

78
@if (_currentUser is null)
@@ -10,7 +11,6 @@
1011
}
1112

1213
<MudLoading @bind-Loading="_isLoading" Darken Overlap>
13-
<ChatClientHub/>
1414
<MudGrid Style="height: 600px">
1515
<MudItem Class="chat-selector-window" md="3" lg="3" xl="3" xxl="3">
1616
<MudList Clickable>
@@ -27,7 +27,7 @@
2727
<MudText>
2828
@if (chat.HasUnreadMessages)
2929
{
30-
<b>@chat.GetDisplayName(_currentUser.Id) (@chat.UnreadMessageCount}</b>
30+
<b>@chat.GetDisplayName(_currentUser.Id) (@chat.UnreadMessageCount)</b>
3131
}
3232
else
3333
{
@@ -50,9 +50,9 @@
5050
@if (_activeChat != null)
5151
{
5252
<MudList Class="chat-message-list" DisablePadding Dense>
53-
@* ReSharper disable once UnusedParameter.Local *@
54-
<Virtualize Items="_activeChat.Messages" Context="message" OverscanCount="8" ItemSize="80">
55-
@if (IsMessageFromSelf(message))
53+
@* ReSharper disable once UnusedParameter.Local *@
54+
<Virtualize Items="_activeChat.Messages" Context="message" OverscanCount="8" ItemSize="80">
55+
@if (IsMessageFromSelf(message))
5656
{
5757
<MudListItem Class="message-from-self">
5858
<MudChip Text="@message.Text"
@@ -73,9 +73,9 @@
7373

7474
</Virtualize>
7575
<MudTextField @bind-Value="_userText"
76-
FullWidth
77-
Immediate
78-
AutoFocus
76+
FullWidth
77+
Immediate
78+
AutoFocus
7979
id="chat-message-input"
8080
Class="chat-message-input"
8181
Adornment="Adornment.End"
@@ -119,7 +119,50 @@
119119

120120
_chats = await ChatService.GetChats(_currentUser.Id);
121121

122-
//TODO: Add SignalR!
122+
ChatSignalRClient.OnChatStarted(chat =>
123+
{
124+
if (chat.InitiatorId == _currentUser.Id)
125+
{
126+
return;
127+
}
128+
129+
var chatDto = chat.ToChatDto();
130+
chatDto.UnreadMessageCount++;
131+
_chats.Insert(0, chatDto);
132+
133+
StateHasChanged();
134+
});
135+
136+
ChatSignalRClient.OnMessageReceived(async message =>
137+
{
138+
var chat = _chats.FirstOrDefault(chat => chat.Id == message.ChatId);
139+
if (chat is null)
140+
{
141+
return;
142+
}
143+
if (message.Sender.Id == _currentUser.Id)
144+
{
145+
// TODO: Message should be shown as sent
146+
return;
147+
}
148+
149+
chat.Messages.Add(message);
150+
151+
if (_activeChat?.Id == chat.Id)
152+
{
153+
await ChatService.MarkMessagesAsRead(message.ChatId);
154+
StateHasChanged();
155+
await ScrollToBottom();
156+
}
157+
else
158+
{
159+
_chats = _chats.OrderByDescending(chat => chat.UnreadMessageCount).ToList();
160+
chat.UnreadMessageCount++;
161+
StateHasChanged();
162+
}
163+
});
164+
await ChatSignalRClient.StartAsync();
165+
123166
_isLoading = false;
124167
}
125168

src/web/Client/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
builder.Configuration.GetSection(DataForsyningenOptions.SectionName));
6767

6868
builder.Services.AddScoped<IChatService, ChatService>();
69+
builder.Services.AddScoped<ChatSignalRClient>();
6970

7071
var host = builder.Build();
7172

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Jordnaer.Client.SignalR;
2+
3+
public interface ISignalRClient : IAsyncDisposable
4+
{
5+
bool IsConnected { get; }
6+
Task StartAsync();
7+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Microsoft.AspNetCore.Components;
2+
using Microsoft.AspNetCore.SignalR.Client;
3+
4+
namespace Jordnaer.Client.SignalR;
5+
6+
public abstract class SignalRClientBase : ISignalRClient
7+
{
8+
protected bool Started { get; private set; }
9+
10+
protected SignalRClientBase(NavigationManager navigationManager, string hubPath)
11+
{
12+
HubConnection = new HubConnectionBuilder()
13+
.WithUrl(navigationManager.ToAbsoluteUri(hubPath))
14+
.WithAutomaticReconnect()
15+
.Build();
16+
}
17+
18+
public bool IsConnected => HubConnection?.State == HubConnectionState.Connected;
19+
20+
protected HubConnection? HubConnection { get; }
21+
22+
public async ValueTask DisposeAsync()
23+
{
24+
if (HubConnection is not null)
25+
{
26+
await HubConnection.DisposeAsync();
27+
}
28+
29+
GC.SuppressFinalize(this);
30+
}
31+
32+
public async Task StartAsync()
33+
{
34+
if (!Started && HubConnection is not null)
35+
{
36+
await HubConnection.StartAsync();
37+
Started = true;
38+
}
39+
}
40+
}

src/web/Server/Features/Profile/ExternalProfilePictureDownloader.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,12 @@ public async ValueTask Handle(AccessTokenAcquired notification, CancellationToke
114114
if (!response.IsSuccessStatusCode)
115115
{
116116
_logger.LogError("Failed to retrieve the Microsoft profile picture for user with id {userId}. " +
117-
"Response: {@response}", userId, response);
117+
"StatusCode: {statusCode}. " +
118+
"Reason: {reasonPhrase}",
119+
userId,
120+
response.StatusCode,
121+
response.ReasonPhrase);
122+
118123
return null;
119124
}
120125

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Jordnaer.Shared;
2+
3+
public static class StartChatExtensions
4+
{
5+
public static ChatDto ToChatDto(this StartChat startChat) =>
6+
new()
7+
{
8+
Id = startChat.Id,
9+
Recipients = startChat.Recipients,
10+
Messages = startChat.Messages,
11+
LastMessageSentUtc = startChat.LastMessageSentUtc,
12+
StartedUtc = startChat.StartedUtc,
13+
DisplayName = startChat.DisplayName,
14+
UnreadMessageCount = 0
15+
};
16+
}

0 commit comments

Comments
 (0)