From efc7f72042773dbd8f8ac4f4eaa445bded475928 Mon Sep 17 00:00:00 2001 From: Peter Maquiran Date: Fri, 2 Aug 2024 11:34:57 +0100 Subject: [PATCH] add reaction to chat --- gabinete-digital-fo.code-workspace | 3 + .../async/socket/message-async.service.ts | 2 +- .../repository/message-respository.service.ts | 7 ++ .../chat/domain/chat-service.service.ts | 9 +- .../message-reaction-use-case.service.ts | 28 ++++++ .../chat/infra/socket/signal-r.service.ts | 35 +++++++- src/app/module/chat/infra/socket/signalR.ts | 85 ++++++++++++++++++- .../shared/chat/messages/messages.page.html | 35 +++++--- .../shared/chat/messages/messages.page.scss | 30 ++++++- src/app/shared/chat/messages/messages.page.ts | 32 ++++++- 10 files changed, 245 insertions(+), 21 deletions(-) create mode 100644 src/app/module/chat/domain/use-case/message-reaction-use-case.service.ts diff --git a/gabinete-digital-fo.code-workspace b/gabinete-digital-fo.code-workspace index d56f8f3c5..4d32d5e79 100644 --- a/gabinete-digital-fo.code-workspace +++ b/gabinete-digital-fo.code-workspace @@ -26,6 +26,9 @@ }, { "path": "../../../Downloads/nestjs-microservice-boilerplate-api-master" + }, + { + "path": "../sqlCliente" } ], "settings": { diff --git a/src/app/module/chat/data/async/socket/message-async.service.ts b/src/app/module/chat/data/async/socket/message-async.service.ts index e512ecdb6..eabbd10f3 100644 --- a/src/app/module/chat/data/async/socket/message-async.service.ts +++ b/src/app/module/chat/data/async/socket/message-async.service.ts @@ -116,7 +116,7 @@ export class MessageAsyncService { if(result.isOk()) { console.log('message exist') - return this.messageLocalDataSourceService.update(result.value) + return this.messageLocalDataSourceService.update({...result.value, ...data}) } else { console.log('message else') } diff --git a/src/app/module/chat/data/repository/message-respository.service.ts b/src/app/module/chat/data/repository/message-respository.service.ts index c0dd4c3d7..01b7546d5 100644 --- a/src/app/module/chat/data/repository/message-respository.service.ts +++ b/src/app/module/chat/data/repository/message-respository.service.ts @@ -104,6 +104,13 @@ export class MessageRepositoryService { return err(false) } + reactToMessage(data) { + this.messageLiveSignalRDataSourceService.sendData({ + method: 'ReactMessage', + data + }) + } + async listAllMessagesByRoomId(id: string) { const result = await this.messageRemoteDataSourceService.getMessagesFromRoom(id) diff --git a/src/app/module/chat/domain/chat-service.service.ts b/src/app/module/chat/domain/chat-service.service.ts index 0aee17a61..42d7fe7ac 100644 --- a/src/app/module/chat/domain/chat-service.service.ts +++ b/src/app/module/chat/domain/chat-service.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { MessageDeleteLiveUseCaseService, MessageDeleteInputDTO } from 'src/app/module/chat/domain/use-case/message-delete-live-use-case.service' import { SessionStore } from 'src/app/store/session.service'; +import { MessageReactionInput, MessageReactionUseCaseService } from 'src/app/module/chat/domain/use-case/message-reaction-use-case.service'; @Injectable({ providedIn: 'root' @@ -8,7 +9,8 @@ import { SessionStore } from 'src/app/store/session.service'; export class ChatServiceService { constructor( - private MessageDeleteLiveUseCaseService: MessageDeleteLiveUseCaseService + private MessageDeleteLiveUseCaseService: MessageDeleteLiveUseCaseService, + private MessageReactionUseCaseService: MessageReactionUseCaseService ) { } messageDelete(data: {roomId, messageId}) { @@ -20,4 +22,9 @@ export class ChatServiceService { return this.MessageDeleteLiveUseCaseService.execute(params) } + + reactToMessage(input: MessageReactionInput) { + return this.MessageReactionUseCaseService.execute(input); + } } + diff --git a/src/app/module/chat/domain/use-case/message-reaction-use-case.service.ts b/src/app/module/chat/domain/use-case/message-reaction-use-case.service.ts new file mode 100644 index 000000000..4dc8ed67d --- /dev/null +++ b/src/app/module/chat/domain/use-case/message-reaction-use-case.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { MessageRepositoryService } from '../../data/repository/message-respository.service'; +import { object, z } from 'zod'; + + +const MessageReactionInputDTOSchema = z.object({ + memberId: z.number(), + messageId: z.string(), + roomId: z.string(), + reaction: z.string(), + requestId: z.string() +}) + +export type MessageReactionInput = z.infer< typeof MessageReactionInputDTOSchema> + +@Injectable({ + providedIn: 'root' +}) +export class MessageReactionUseCaseService { + + constructor( + public repository: MessageRepositoryService + ) { } + + execute(input: MessageReactionInput) { + return this.repository.reactToMessage(input) + } +} diff --git a/src/app/module/chat/infra/socket/signal-r.service.ts b/src/app/module/chat/infra/socket/signal-r.service.ts index 45ef02bba..93c76db41 100644 --- a/src/app/module/chat/infra/socket/signal-r.service.ts +++ b/src/app/module/chat/infra/socket/signal-r.service.ts @@ -3,15 +3,23 @@ import { BehaviorSubject } from 'rxjs'; import { Platform } from '@ionic/angular'; import { SignalRConnection } from './signalR'; import { Plugins } from '@capacitor/core'; -import { z } from 'zod'; import { UserTypingDTO } from '../../data/dto/typing/typingInputDTO'; import { MessageOutPutDataDTO } from '../../data/dto/message/messageOutputDTO'; import { MessageDeleteInputDTO } from '../../data/dto/message/messageDeleteInputDTO'; +import { object, z } from 'zod'; const { App } = Plugins; +const SignalRInputSchema = z.object({ + method: z.string(), + data: z.object({ + requestId: z.string() + }) +}) +export type ISignalRInput = z.infer + @Injectable({ providedIn: 'root' }) @@ -22,6 +30,8 @@ export class SignalRService { private connectingSubject: BehaviorSubject = new BehaviorSubject(null); private messageDelete: BehaviorSubject = new BehaviorSubject(null); private messageUpdateSubject: BehaviorSubject = new BehaviorSubject(null); + private sendDataSubject: BehaviorSubject = new BehaviorSubject(false); + constructor( private platform: Platform) { @@ -78,6 +88,14 @@ export class SignalRService { this.connection.getMessageUpdateSubject().subscribe((data) => { this.messageUpdateSubject.next(data) }) + + this.connection.getMessageUpdateSubject().subscribe((data) => { + this.messageUpdateSubject.next(data) + }) + + this.connection.getData().subscribe((data) => { + this.messageUpdateSubject.next(data) + }) } } @@ -100,6 +118,14 @@ export class SignalRService { return this.messageUpdateSubject.asObservable() } + sendData(input: ISignalRInput) { + return this.connection.sendData(input) + } + + getData() { + return this.getData() + } + async sendMessage(data: Object) { return await this.connection.sendMessage(data as any) } @@ -120,5 +146,10 @@ export class SignalRService { async sendMessageDelete(data: MessageDeleteInputDTO) { return await this.connection.deleteMessage(data) } - + + + async sendReactToMessage(data) { + return await this.connection.sendReactMessage(data) + } + } diff --git a/src/app/module/chat/infra/socket/signalR.ts b/src/app/module/chat/infra/socket/signalR.ts index 9b6efcb75..4eab85466 100644 --- a/src/app/module/chat/infra/socket/signalR.ts +++ b/src/app/module/chat/infra/socket/signalR.ts @@ -7,6 +7,8 @@ import { v4 as uuidv4 } from 'uuid' import { UserTypingDTO } from '../../data/dto/typing/typingInputDTO'; import { MessageOutPutDataDTO } from '../../data/dto/message/messageOutputDTO'; import { MessageDeleteInputDTO } from '../../data/dto/message/messageDeleteInputDTO'; +import { MessageReactionInput } from '../../domain/use-case/message-reaction-use-case.service'; +import { ISignalRInput } from './signal-r.service'; export class SignalRConnection { @@ -21,6 +23,9 @@ export class SignalRConnection { private reconnectSubject: BehaviorSubject = new BehaviorSubject(false); private sendLaterSubject: BehaviorSubject = new BehaviorSubject(false); private reconnect = true + + private sendDataSubject: BehaviorSubject = new BehaviorSubject(false); + url: string constructor({url}) { @@ -118,7 +123,7 @@ export class SignalRConnection { this.messageSubject.pipe( filter((message: any) => data.requestId == message?.requestId), - first() + first() ).subscribe((value) => { resolve(ok(value)) }) @@ -157,7 +162,6 @@ export class SignalRConnection { }) } - public async sendReadAt(data: Object & { roomId, memberId, chatMessageId}):Promise> { return new Promise((resolve, reject) => { @@ -187,31 +191,104 @@ export class SignalRConnection { }) } + + public async sendReactMessage(data: MessageReactionInput):Promise> { + return new Promise((resolve, reject) => { + + const requestId = uuidv4() + if(this.connectionStateSubject.value == true) { + + try { + this.hubConnection.invoke("ReactMessage", { roomId: data.roomId, memberId: data.memberId, requestId, messageId: data.messageId} as any) + + } catch (error) {} + + this.messageUPdateSubject.pipe( + filter((message: any) => { + return requestId == message?.requestId + }), + first() + ).subscribe(value => { + resolve(ok(value)); + }); + + } else { + this.sendLaterSubject.next({method: 'SendMessage', args: data}) + return reject(err(false)) + } + + }) + } + + + sendData(input: ISignalRInput) { + return new Promise((resolve, reject) => { + + if(this.connectionStateSubject.value == true) { + + this.hubConnection.invoke(input.method, input.data) + + this.sendDataSubject.pipe( + filter((message: any) => input.data.requestId == message?.requestId), + first() + ).subscribe(value => { + resolve(ok(value)) + console.log('Received valid value:', value); + }); + + } else { + this.sendLaterSubject.next({method: 'SendMessage', args: input}) + return reject(err(false)) + } + + }) + } + private addMessageListener(): void { console.log('listening') this.hubConnection.on('ReceiveMessage', (message: MessageOutPutDataDTO) => { console.log('ReceiveMessage', message) this.messageSubject.next(message); + this.sendDataSubject.next({ + method: 'ReceiveMessage', + data: message + }) }); this.hubConnection.on('TypingMessage', (_typing: UserTypingDTO) => { console.log('Typing', _typing) this.typingSubject.next(_typing); + this.sendDataSubject.next({ + method: 'ReceiveMessage', + data: _typing + }) }); this.hubConnection.on('ReadAt', (_message) => { console.log('ReadAt', _message) this.readAtSubject.next(_message); + this.sendDataSubject.next({ + method: 'ReceiveMessage', + data: _message + }) }); this.hubConnection.on('DeleteMessage', (_message) => { console.log('DeleteMessage', _message) this.messageDelete.next(_message); + this.sendDataSubject.next({ + method: 'ReceiveMessage', + data: _message + }) }); this.hubConnection.on('UpdateMessage', (_message) => { console.log('UpdateMessage', _message) this.messageUPdateSubject.next(_message); + this.sendDataSubject.next({ + method: 'ReceiveMessage', + data: _message + }) }) } @@ -244,6 +321,10 @@ export class SignalRConnection { return this.messageDelete.asObservable() } + public getData() { + return this.sendDataSubject.asObservable() + } + public closeConnection(): void { this.reconnect = false if (this.hubConnection) { diff --git a/src/app/shared/chat/messages/messages.page.html b/src/app/shared/chat/messages/messages.page.html index b2206d2fe..ebfd690fe 100644 --- a/src/app/shared/chat/messages/messages.page.html +++ b/src/app/shared/chat/messages/messages.page.html @@ -44,21 +44,34 @@
-
- {{ message.message }} .== {{ message.id }} + *ngFor="let message of roomMessage$ | async" class="messages-list-item-wrapper" + [ngClass]="{'my-message': message.sender.wxUserId === sessionStore.user.UserId, 'other-message': message.sender.wxUserId !== sessionStore.user.UserId}"> +
+ {{ message.message }} .== {{ message.id }} -
- - - - - -
+
+ + + + +
+ + +
+ + {{ emoji }} + +
+ +
+ + {{ reaction.reaction }} + +
+
diff --git a/src/app/shared/chat/messages/messages.page.scss b/src/app/shared/chat/messages/messages.page.scss index 40e8b56fa..a7e20c885 100644 --- a/src/app/shared/chat/messages/messages.page.scss +++ b/src/app/shared/chat/messages/messages.page.scss @@ -160,8 +160,9 @@ ion-content { // Common styles for incoming messages display: flex; - justify-content: flex-end; - align-items: center; + /* justify-content: flex-end; */ + align-items: end; + flex-direction: column; //margin-bottom: 10px; // Adjust as needed //padding: 5px; // Adjust as needed .message-container { @@ -443,3 +444,28 @@ ion-footer { //display: block !important; /* Show the options on hover */ opacity: 1 !important; } + + + +.message-item-options { + position: relative; +} + +.emoji-picker { + display: flex; + flex-wrap: wrap; + background: white; + border: 1px solid #ccc; + padding: 5px; + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + -o-border-radius: 5px; +} + +.emoji-icon { + font-size: 20px; + cursor: pointer; + margin: 2px; +} diff --git a/src/app/shared/chat/messages/messages.page.ts b/src/app/shared/chat/messages/messages.page.ts index f113453f0..b4dbfdc43 100644 --- a/src/app/shared/chat/messages/messages.page.ts +++ b/src/app/shared/chat/messages/messages.page.ts @@ -127,6 +127,10 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy userTyping$: UserTypingList[] | undefined newMessagesStream!: Subscription + selectedMessage: any = null; + emojis: string[] = ['😊', '😂', '❤️', '👍', '😢']; // Add more emojis as needed + + constructor( public popoverController: PopoverController, private modalController: ModalController, @@ -193,12 +197,36 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy // }) } + + toggleEmojiPicker(message: any) { + if (this.selectedMessage === message) { + this.selectedMessage = null; // Close the picker if it's already open + } else { + this.selectedMessage = message; // Open the picker for the selected message + } + } + + addReaction(message: any, emoji: string) { + // Logic to add reaction to the message + console.log(`Reacting to message ${message.id} with emoji ${emoji.codePointAt(0).toString(16)}`); + this.selectedMessage = null; // Close the picker after adding reaction + + this.chatServiceService.reactToMessage({ + memberId: SessionStore.user.UserId, + messageId: message.messageId, + roomId: this.roomId, + reaction: emoji, + requestId: '' + }) + + } + sendReadAt() { this.messageRepositoryService.sendReadAt({roomId: this.roomId}).then((e) => { console.log(e) }) } - + sendTyping() { this.userTypingServiceRepository.addUserTyping(this.roomId) } @@ -922,7 +950,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy messageDelete({messageId}) { // this.messageRepositoryService.sendMessageDelete() - + this.chatServiceService.messageDelete({ messageId: messageId, roomId: this.roomId,