Skip to content

Commit

Permalink
Merge pull request #216 from dancier/feature/improvements
Browse files Browse the repository at this point in the history
Feature/improvements
  • Loading branch information
halbekanne authored Jan 7, 2024
2 parents 6610e35 + c9e2827 commit 7332c6e
Show file tree
Hide file tree
Showing 17 changed files with 220 additions and 138 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ speed-measure-plugin*.json
*.launch
.settings/
*.sublime-workspace
.fleet/

# IDE - VSCode
.vscode/*
Expand Down
48 changes: 24 additions & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
"@angular/router": "^16.1.4",
"@ngneat/until-destroy": "^9.2.2",
"@ngrx/component-store": "^16.1.0",
"@state-adapt/angular": "^1.2.1",
"@state-adapt/core": "^1.2.1",
"@state-adapt/rxjs": "^1.2.1",
"@state-adapt/angular": "^2.0.5",
"@state-adapt/core": "^2.0.5",
"@state-adapt/rxjs": "^2.0.5",
"bootstrap-icons": "^1.11.2",
"date-fns": "^2.29.3",
"hammerjs": "^2.0.8",
Expand Down
94 changes: 56 additions & 38 deletions src/app/chat/data-access/chat-state.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,27 +74,30 @@ export const chatStateAdapter = createAdapter<ChatAdaptState>()({
activeChatId: chatId,
}),

chatMessagesFetched: (state, { messages, chatId }: MessagesWithChatId) => ({
...state,
newMessageSent: false,
chats: state.chats.map((chat) => {
// only add new messages to active chat
if (chat.id !== chatId) return chat;
return {
...chat,
messages: [
...chat.messages.map((message) => {
const updatedMessage = messages.find((m) => m.id === message.id);
if (!updatedMessage) return message;
return updateMessage(message, updatedMessage);
}),
...messages.filter(
(message) => !chat.messages.find((m) => m.id === message.id)
),
],
};
}),
}),
chatMessagesFetched: (state, { messages, chatId }: MessagesWithChatId) => {
messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
return {
...state,
newMessageSent: false,
chats: state.chats.map((chat) => {
// only add new messages to active chat
if (chat.id !== chatId) return chat;
return {
...chat,
messages: [
...chat.messages.map((message) => {
const updatedMessage = messages.find((m) => m.id === message.id);
if (!updatedMessage) return message;
return updateMessage(message, updatedMessage);
}),
...messages.filter(
(message) => !chat.messages.find((m) => m.id === message.id)
),
],
};
}),
};
},

openChatWith: (state, dancerId: string) => ({
...state,
Expand Down Expand Up @@ -128,8 +131,28 @@ export const chatStateAdapter = createAdapter<ChatAdaptState>()({
...state,
}),

setMessagesAsRead: (state, _chatId: string) => ({
setMessagesAsRead: (state, { chatId, profileId }) => ({
...state,
chats: state.chats.map((chat) => {
if (chat.id !== chatId) return chat;
return {
...chat,
lastMessage: chat.lastMessage
? {
...chat.lastMessage,
readByParticipants: [
...(chat.lastMessage.readByParticipants || []),
profileId,
],
}
: null,
};
}),
}),

profileIdChanged: (state, profileId: string | undefined) => ({
...state,
ownProfileId: profileId,
}),

selectors: {
Expand All @@ -156,6 +179,16 @@ export const chatStateAdapter = createAdapter<ChatAdaptState>()({
return activeChat?.participants ?? [];
},
newMessageSent: (state) => state.newMessageSent,
activeChatUnreadMessages: (state) => {
const activeChat = state.chats.find(
(chat) => chat.id === state.activeChatId
);
if (!activeChat) return [];
return activeChat.messages.filter(
(message) =>
message.readByParticipants && message.readByParticipants.length < 2
);
},
},
});

Expand All @@ -180,24 +213,9 @@ function updateChat(
): SingleChatState {
// for each property in newChat, update oldChat, only if they are different
if (
JSON.stringify(oldChat.lastMessage) === JSON.stringify(newChat.lastMessage)
JSON.stringify(oldChat.lastMessage) !== JSON.stringify(newChat.lastMessage)
) {
oldChat.lastMessage = newChat.lastMessage;
}
return oldChat;

// Object.keys(newChat).forEach((key: string) => {
// const keyOfChat = key as keyof SingleChatState;
// if (keyOfChat !== 'messages' && newChat[keyOfChat] !== oldChat[keyOfChat]) {
// console.log(
// 'updating',
// keyOfChat,
// newChat[keyOfChat],
// oldChat[keyOfChat]
// );
// const newValue = newChat[keyOfChat];
// oldChat[keyOfChat] = newValue as any;
// }
// });
return oldChat;
}
65 changes: 36 additions & 29 deletions src/app/chat/data-access/chat-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { getRequestSources, Source, toSource } from '@state-adapt/rxjs';
import { ChatMessage, ChatParticipant } from './chat.types';
import { toSignal } from '@angular/core/rxjs-interop';
import {
distinct,
debounceTime,
distinctUntilChanged,
filter,
map,
merge,
of,
switchMap,
throttleTime,
withLatestFrom,
} from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
Expand All @@ -36,6 +38,7 @@ export type ChatAdaptState = {
openChatWithParticipantId: string | null;
chatCreated: boolean;
newMessageSent: boolean;
ownProfileId: string | undefined;
};

@Injectable()
Expand Down Expand Up @@ -64,6 +67,11 @@ export class ChatStateService {

sendMessage$ = new Source<string>('[Chat] sendMessage');

profileIdChanged$ = this.profileService.profile$.pipe(
map((profile) => profile?.id),
toSource('[Chat] profileChanged')
);

// Adapters
//
private readonly initialState: ChatAdaptState = {
Expand All @@ -74,18 +82,20 @@ export class ChatStateService {
openChatWithParticipantId: null,
chatCreated: false,
newMessageSent: false,
ownProfileId: undefined,
};

private chatStore = adapt(
[this.storePath, this.initialState, chatStateAdapter],
(store) => {
private chatStore = adapt(this.initialState, {
path: this.storePath,
adapter: chatStateAdapter,
sources: (store) => {
const timerService = inject(TimerService);
const chatHttpService = inject(ChatHttpService);

const fetchChatsSources = getRequestSources(
'[Chat] fetchChats',
merge(
timerService.interval('chatFetchTrigger', 10000),
timerService.interval('chatFetchTrigger', 20000),
store.chatCreated$.pipe(filter((hasCreated) => !!hasCreated))
).pipe(
startWith(-1),
Expand All @@ -97,6 +107,7 @@ export class ChatStateService {
'[Chat] fetchParticipantDetails',
store.participantsWithNoDetails$.pipe(
filter((participants) => participants.length > 0),
debounceTime(250),
map((participants: ChatParticipant[]) =>
participants.map((p) => p.id)
),
Expand All @@ -110,7 +121,8 @@ export class ChatStateService {
'[Chat] fetchMessages',
merge(
timerService.interval('chatMessagesFetchTrigger', 5000),
store.activeChatId$.pipe(distinct())
store.activeChatId$.pipe(distinctUntilChanged()),
store.newMessageSent$.pipe(filter((hasSent) => !!hasSent))
).pipe(
switchMap(() => store.activeChatId$),
filter((chatId) => chatId !== null),
Expand Down Expand Up @@ -157,30 +169,24 @@ export class ChatStateService {
)
);

const setMessagesAsReadSource = store.activeChatId$.pipe(
filter((chatId) => chatId !== null),
filter(() => this.profileService.getProfile()?.id !== undefined),
distinct(),
switchMap((chatId) =>
store.chats$.pipe(
map((chats) => chats.find((chat) => chat.id === chatId)),
filter((chat) => chat !== undefined),
map((chat) => ({
chatId: chat!.id,
unreadMessages: chat!.messages.filter(
(message) =>
!message.readByParticipants?.includes(
this.profileService.getProfile()!.id!
)
),
}))
)
),
switchMap(({ chatId, unreadMessages }) => {
const distinctUnreadMessages = store.activeChatUnreadMessages$.pipe(
distinctUntilChanged<ChatMessage[]>(
(curr, prev) => JSON.stringify(curr) === JSON.stringify(prev)
)
);

const setMessagesAsReadSource = distinctUnreadMessages.pipe(
filter((messages) => messages.length > 0),
withLatestFrom(store.activeChatId$),
throttleTime(250),
switchMap(([unreadMessages, chatId]) => {
for (const message of unreadMessages) {
chatHttpService.setMessageAsRead(message.id).subscribe();
}
return of(chatId);
return of({
chatId: chatId,
profileId: this.profileService.getProfile()!.id!,
});
}),
toSource('[Chat] setMessagesAsRead')
);
Expand All @@ -200,10 +206,11 @@ export class ChatStateService {
messageSent: sendMessageSource.success$,
messageSentError: sendMessageSource.error$,
setMessagesAsRead: setMessagesAsReadSource,
profileIdChanged: this.profileIdChanged$,
reset: this.userLoggedOut$,
};
}
);
},
});

// Selectors
// public signals for components to consume
Expand Down
Loading

0 comments on commit 7332c6e

Please sign in to comment.