add chat to mobile

This commit is contained in:
Peter Maquiran
2024-08-08 11:44:41 +01:00
parent 6d8ef9b1e3
commit 32181caefe
12 changed files with 306 additions and 231 deletions
@@ -3,7 +3,7 @@ import { HttpService } from 'src/app/services/http.service';
import { MessageInputDTO, MessageInputDTOSchema } from '../../dto/message/messageInputDtO';
import { ValidateSchema } from 'src/app/services/decorators/validate-schema.decorator';
import { APIReturn } from 'src/app/services/decorators/api-validate-schema.decorator';
import { MessageOutPutDTO, MessageOutPutDTOSchema } from '../../dto/message/messageOutputDTO';
import { MessageOutPutDataDTOSchema, MessageOutPutDTO, MessageOutPutDTOSchema } from '../../dto/message/messageOutputDTO';
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
@Injectable({
@@ -15,7 +15,7 @@ export class MessageRemoteDataSourceService {
constructor(private httpService: HttpService) {}
@APIReturn(MessageOutPutDTOSchema, 'get/Messages')
@APIReturn(MessageOutPutDTOSchema, 'post/Messages')
@ValidateSchema(MessageInputDTOSchema)
async sendMessage(data: MessageInputDTO) {
return await this.httpService.post<MessageOutPutDTO>(`${this.baseUrl}/Messages`, data);
@@ -25,6 +25,7 @@ export class MessageRemoteDataSourceService {
return await this.httpService.post<any>(`${this.baseUrl}/Messages/${id}/React`, reaction);
}
@APIReturn(MessageOutPutDTOSchema, 'get/Messages')
async getMessagesFromRoom(id: string): DataSourceReturn<MessageOutPutDTO> {
return await this.httpService.get(`${this.baseUrl}/Room/${id}/Messages`);
}
@@ -12,24 +12,23 @@ export enum MessageAttachmentFileType {
Video
}
const DataSchema = z.object({
export const MessageOutPutDataDTOSchema = z.object({
id: z.string(),
roomId: z.string(),
wxUserId: z.number(),
sender: z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string().optional()
}).nullable(),
message: z.string(),
message: z.string().nullable(),
messageType: z.number(),
sentAt: z.string(),
deliverAt: z.string().datetime().nullable(),
canEdit: z.boolean(),
oneShot: z.boolean(),
requireUnlock: z.boolean(),
requestId: z.string(),
requestId: z.string().optional(),
reactions: z.object({
id: z.string(),
reactedAt: z.string(),
@@ -50,8 +49,8 @@ const DataSchema = z.object({
export const MessageOutPutDTOSchema = z.object({
success: z.boolean(),
message: z.string(),
data: DataSchema.array()
data: MessageOutPutDataDTOSchema.array()
});
export type MessageOutPutDataDTO = z.infer<typeof DataSchema>
export type MessageOutPutDataDTO = z.infer<typeof MessageOutPutDataDTOSchema>
export type MessageOutPutDTO = z.infer<typeof MessageOutPutDTOSchema>
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { z } from 'zod';
import { MessageRepositoryService } from '../../data/repository/message-respository.service';
import { ValidateSchema } from 'src/app/services/decorators/validate-schema.decorator';
import { SafeValidateSchema, ValidateSchema } from 'src/app/services/decorators/validate-schema.decorator';
export const MessageDeleteInputDTOSchema = z.object({
requestId: z.string().optional(),
@@ -20,7 +20,7 @@ export class MessageDeleteLiveUseCaseService {
public repository: MessageRepositoryService
) { }
@ValidateSchema(MessageDeleteInputDTOSchema)
@SafeValidateSchema(MessageDeleteInputDTOSchema, 'MessageDeleteLiveUseCaseService')
async execute(data: MessageDeleteInputDTO) {
return this.repository.sendMessageDelete(data)
}
@@ -1,6 +1,8 @@
import { Injectable } from '@angular/core';
import { MessageLocalDataSourceService } from '../../../data/data-source/message/message-local-data-source.service';
import { MessageOutPutDataDTO } from '../../../data/dto/message/messageOutputDTO';
import { MessageOutPutDataDTO, MessageOutPutDataDTOSchema } from '../../../data/dto/message/messageOutputDTO';
import { SafeValidateSchema, ValidateSchema } from 'src/app/services/decorators/validate-schema.decorator';
import { MessageInputDTOSchema } from '../../../data/dto/message/messageInputDtO';
@Injectable({
providedIn: 'root'
@@ -12,6 +14,7 @@ export class SocketMessageUpdateUseCaseService {
) { }
@SafeValidateSchema(MessageOutPutDataDTOSchema, 'SocketMessageUpdateUseCaseService')
async execute(data: MessageOutPutDataDTO) {
const result = await this.messageLocalDataSourceService.messageExist({id: data.id})
@@ -23,7 +26,7 @@ export class SocketMessageUpdateUseCaseService {
// delete data.chatRoomId
if(result.isOk()) {
console.log('message exist', result.value)
console.log('message exist', result.value, incomingMessage)
return this.messageLocalDataSourceService.update(result.value.$id, incomingMessage)
} else {
console.log('message else')
@@ -5,7 +5,7 @@ export const MessageTable = z.object({
$id: z.number().optional(),
id: z.string().optional(),
roomId: z.string().uuid(),
message: z.string(),
message: z.string().nullable(),
messageType: z.number(),
canEdit: z.boolean(),
oneShot: z.boolean(),
+82 -199
View File
@@ -1,4 +1,4 @@
<ion-header class="ion-no-border" *ngIf="ChatSystemService.getDmRoom(roomId)">
<ion-header class="ion-no-border" >
<ion-toolbar class="header-toolbar">
<div class="main-header">
<div class="header-top">
@@ -9,10 +9,10 @@
</button>
</div>
<div class="middle-container" *ngIf="!showMessageOptions">
<div class="middle">
<ion-label class="title">{{ChatSystemService.getDmRoom(roomId).name}}</ion-label>
<span><ion-icon *ngIf="RochetChatConnectorService.isLogin"
class="{{ ChatSystemService.getDmRoom(this.roomId).online }}" name="ellipse"></ion-icon></span>
<div class="middle" *ngIf="roomData$ | async as roomData">
<ion-label class="title">{{ roomData.roomName }}</ion-label>
<span *ngIf="roomStatus$ | async as roomStatus"><ion-icon *ngIf="roomStatus"
class="online" name="ellipse"></ion-icon></span>
</div>
</div>
@@ -42,219 +42,102 @@
</ion-toolbar>
</ion-header>
<ion-content *ngIf="ChatSystemService.getDmRoom(roomId)">
<div (click)="handleClick()" class="message-attachments messages" #scrollMe>
<ion-content >
<div class="messages-list-item-wrapper container-width-100 file" (dragover)="onDragOver($event)"
(dragleave)="onDragLeave($event)"
*ngFor="let msg of ChatSystemService.getDmRoom(this.roomId).messages; let last = last"
[class.messages-list-item-wrapper-active]="msg._id == selectedMsgId">
<div (press)="handlePress(msg._id)"
class='file message-container incoming-{{msg.u.username!=sessionStore.user.UserName}}' *ngIf="msg.msg !=''"
[class.dateLabel]="msg.dateLabel">
<div class="title file" *ngIf="!msg.dateLabel">
<ion-label>{{msg.u.name}}</ion-label>
<span class="time">{{msg.time}}</span>
</div>
<div class="messages height-100 width-100 d-flex flex-column" #scrollMe >
<div
*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}"
>
<div class="message-container">
<div class="d-flex justify-content-between">
<div>
<div class="file">
<pre *ngIf="msg.delate == false" class="message-box text ma-0 font-13-rem"
style="font-size: 0.8125rem !important;">{{msg.msg}}</pre>
<!-- <ion-label *ngIf="msg.delate == false" class="message-box">{{msg.msg}} </ion-label> -->
<ion-label *ngIf="msg.delate == true" class="flex-0">Apagou a mensagem</ion-label>
<ion-label class="float-status-all float-status" *ngIf="msg.u.username==sessionStore.user.UserName">
<span *ngIf="msg.online == true && !msg.manualRetry && msg.viewed == 0" class="enviado pl-10">
Enviado</span>
<!-- <ion-icon *ngIf="msg.messageSend == false && !msg.manualRetry" src="assets/images/clock-regular.svg"></ion-icon> -->
<!-- <ion-icon *ngIf="msg.messageSend == true && msg.received.length == 0 && msg.viewed.length == 0" src="assets/images/check-solid.svg"></ion-icon> -->
<!-- <ion-icon *ngIf="msg.messageSend && msg.received.length >= 1 && msg.viewed.length == 0" src="assets/images/check-double-solid.svg"></ion-icon> -->
<!-- <ion-icon *ngIf="msg.viewed.length >= 1" src="assets/images/check-double-solid -viewed.svg"></ion-icon> -->
<span class="lido pl-10" *ngIf="msg.viewed.length >= 1"> Lido</span>
<div *ngIf="msg.manualRetry" class="try pl-10" (click)="msg.send()">Tentar</div>
</ion-label>
</div>
{{last ? scrollToBottom() : ''}}
{{ message.message }}
</div>
</div>
<div *ngIf="msg.file && msg.delate == false">
<div (press)="handlePress(msg._id)"
class='message-container incoming-{{msg.u.username!=sessionStore.user.UserName}}'
*ngIf="msg.file.type != 'application/meeting'">
<div class="title">
<ion-label>{{msg.u.name}}</ion-label>
<span class="time">{{msg.time}}</span>
<div>
<div class="message-item-options d-flex justify-content-end">
<fa-icon [matMenuTriggerFor]="beforeMenu" icon="chevron-down" class="message-options-icon cursor-pointer"></fa-icon>
<mat-menu #beforeMenu="matMenu" xPosition="before">
<button (click)="messageDelete({messageId: message.id })" class="menuButton">Apagar mensagem</button>
<button (click)="editMessage(message)" class="menuButton">Editar mensagem</button>
<button (click)="toggleEmojiPicker(message)" class="menuButton">Reagir mensagem</button>
</mat-menu>
</div>
<div>
<div *ngIf="msg.attachments" class="message-attachments">
<div *ngFor="let file of msg.attachments let i = index">
<div class="file" *ngIf="msg.file.type == 'application/img'" (click)="openPreview(msg)">
<div *ngIf="!msg.attachments[0].image_url">
<ion-item class="add-attachment-bg-color" shape="round" lines="none" type="button">
<ion-icon name="image" class="file-icon"></ion-icon>
<ion-label>{{"Imagem"}}</ion-label>
<ion-icon
*ngIf="ThemeService.currentTheme == 'gov' && msg.downloadLoader == false && msg.uploadingFile == false && msg.downloadAttachmentsTemp == 0"
class="icon-download" src="assets/icon/theme/{{ThemeService.currentTheme}}/icons-download.svg"
slot="end"></ion-icon>
<ion-icon *ngIf="( msg.downloadLoader == true || msg.uploadingFile == true )"
class="icon-download" src="assets/gif/theme/{{ThemeService.currentTheme}}/Blocks-loader.svg"
slot="end"></ion-icon>
<ion-icon
*ngIf="msg.downloadAttachments == false && msg.downloadAttachmentsTemp >= 1 && msg.downloadLoader == false"
src="assets/images/retry-svgrepo-com.svg" class="icon-download font-12"> </ion-icon>
</ion-item>
</div>
<img class="d-block width-100" *ngIf="msg.attachments[0].image_url"
src="{{msg.attachments[0].image_url}}" alt="image">
<ion-label class="float-status-all float-status" *ngIf="msg.u.username==sessionStore.user.UserName">
<span *ngIf="msg.online == true && !msg.manualRetry && msg.viewed == 0" class="enviado pl-10">
Enviado</span>
<!-- <ion-icon *ngIf="msg.messageSend == false && !msg.manualRetry" src="assets/images/clock-regular.svg"></ion-icon> -->
<!-- <ion-icon *ngIf="msg.messageSend == true && msg.received.length == 0 && msg.viewed.length == 0" src="assets/images/check-solid.svg"></ion-icon> -->
<!-- <ion-icon *ngIf="msg.messageSend && msg.received.length >= 1 && msg.viewed.length == 0" src="assets/images/check-double-solid.svg"></ion-icon> -->
<!-- <ion-icon *ngIf="msg.viewed.length >= 1" src="assets/images/check-double-solid -viewed.svg"></ion-icon> -->
<span class="lido pl-10" *ngIf="msg.viewed.length >= 1"> Lido</span>
<div *ngIf="msg.manualRetry" class="try pl-10" (click)="msg.send()">Tentar</div>
</ion-label>
</div>
<div *ngIf="msg.file.type != 'application/img'">
<div class="file add-attachment-bg-color" *ngIf="msg.file.type != 'application/audio'">
<div (click)="docIndex(i); openPreview(msg)" class="file-details add-ellipsis cursor-pointer"
*ngIf="msg.file">
<div *ngIf="!msg.attachments[0].image_url">
<ion-item class="add-attachment-bg-color" shape="round" lines="none" type="button">
<ion-icon *ngIf="msg.attachments[0].type != 'webtrix'" name="document"
class="file-icon"></ion-icon>
<ion-icon *ngIf="msg.attachments[0].type == 'webtrix'" src="assets/icon/webtrix.svg"
class="file-icon"></ion-icon>
<ion-label>{{ file.title}}</ion-label>
<ion-icon
*ngIf="ThemeService.currentTheme == 'default' && msg.attachments[0].type != 'webtrix' && !( msg.downloadLoader == true || msg.uploadingFile == true ) "
class="icon-download" src="assets/icon/theme/default/icons-download.svg"
slot="end"></ion-icon>
<ion-icon
*ngIf="ThemeService.currentTheme == 'gov' && msg.attachments[0].type != 'webtrix' && !( msg.downloadLoader == true || msg.uploadingFile == true ) "
class="icon-download" src="assets/icon/theme/gov/icons-download.svg" slot="end"></ion-icon>
<ion-icon *ngIf="( msg.downloadLoader == true || msg.uploadingFile == true )"
class="icon-download" src="assets/gif/theme/{{ThemeService.currentTheme}}/Blocks-loader.svg"
slot="end"></ion-icon>
</ion-item>
</div>
<div *ngIf="msg.attachments[0].image_url">
<span *ngIf="msg.file.type">
<fa-icon *ngIf="msg.file.type == 'application/pdf'" icon="file-pdf"
class="pdf-icon"></fa-icon>
<fa-icon
*ngIf="msg.file.type == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'"
icon="file-word" class="word-icon"></fa-icon>
<fa-icon *ngIf="msg.file.type == 'application/word'" icon="file-word" class="word-icon">
</fa-icon>
<fa-icon
*ngIf="msg.file.type == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'"
icon="file-word" class="excel-icon"></fa-icon>
<ion-icon *ngIf="msg.file.type == 'application/webtrix'" src="assets/icon/webtrix.svg">
</ion-icon>
</span>
<ion-label class="file-title">{{file.title}}</ion-label>
</div>
<!-- <ion-progress-bar [type]="'indeterminate'" *ngIf="downloadLoader"></ion-progress-bar> -->
</div>
</div>
<div (click)="audioPreview(msg)" class="audio-contentainer"
*ngIf="msg.file.type == 'application/audio' && !file.title_link">
<ion-item class="add-attachment-bg-color" shape="round" lines="none" type="button">
<ion-icon name="mic-outline" class="file-icon"></ion-icon>
<ion-label>{{"Mensagem de voz"}}</ion-label>
<ion-icon
*ngIf="ThemeService.currentTheme == 'gov' && msg.downloadLoader == false && msg.uploadingFile == false && msg.downloadAttachmentsTemp == 0"
class="icon-download" src="assets/icon/theme/{{ThemeService.currentTheme}}/icons-download.svg"
slot="end"></ion-icon>
<ion-icon *ngIf="( msg.downloadLoader == true || msg.uploadingFile == true )"
class="icon-download" src="assets/gif/theme/{{ThemeService.currentTheme}}/Blocks-loader.svg"
slot="end"></ion-icon>
<ion-icon
*ngIf="msg.downloadAttachments == false && msg.downloadAttachmentsTemp >= 1 && msg.downloadLoader == false"
src="assets/images/retry-svgrepo-com.svg" class="icon-download font-12"> </ion-icon>
</ion-item>
</div>
<div class="file audio-contentainer" *ngIf="msg.file.type == 'application/audio' && file.title_link">
<audio [src]="file.title_link | safehtml" preload="metadata" class="d-flex width-100" controls
controlsList="nodownload noplaybackrate"></audio>
</div>
<div class="file-details-optional add-attachment-bg-color">
<ion-label *ngIf="msg.file && msg.file != ''">
<span *ngIf="file.description">{{file.description}}</span>
<span *ngIf="file.description && msg.file.type != 'application/webtrix'"></span>
</ion-label>
<ion-label class="float-status-all float-status" *ngIf="msg.u.username==sessionStore.user.UserName">
<span *ngIf="msg.online == true && !msg.manualRetry && msg.viewed == 0" class="enviado pl-10">
Enviado</span>
<!-- <ion-icon *ngIf="msg.messageSend == false && !msg.manualRetry" src="assets/images/clock-regular.svg"></ion-icon> -->
<!-- <ion-icon *ngIf="msg.messageSend == true && msg.received.length == 0 && msg.viewed.length == 0" src="assets/images/check-solid.svg"></ion-icon> -->
<!-- <ion-icon *ngIf="msg.messageSend && msg.received.length >= 1 && msg.viewed.length == 0" src="assets/images/check-double-solid.svg"></ion-icon> -->
<!-- <ion-icon *ngIf="msg.viewed.length >= 1" src="assets/images/check-double-solid -viewed.svg"></ion-icon> -->
<span class="lido pl-10" *ngIf="msg.viewed.length >= 1"> Lido</span>
<div *ngIf="msg.manualRetry" class="try pl-10" (click)="msg.send()">Tentar</div>
</ion-label>
</div>
</div>
</div>
</div>
{{last ? scrollToBottom() : ''}}
</div>
</div>
<div *ngIf="msg.file.type == 'application/meeting'" class="info-meeting">
<ion-label class="info-meeting-small">{{msg.u.name}} criou esta reunião</ion-label><br />
<button (click)="goToEvent(msg.file)" class="btn-no-color info-meeting-normal">
<ion-label class="info-meeting-normal">{{msg.file.subject}}</ion-label>
</button><br />
<ion-label class="info-meeting-medium">
<ion-icon name="calendar-outline"></ion-icon> De {{showDateDuration(msg.file.start_date)}} a
{{showDateDuration(msg.file.end_date)}}
</ion-label><br />
<ion-label class="info-meeting-medium">
<ion-icon></ion-icon>
<ion-icon name="location-outline"></ion-icon> {{msg.file.venue}}
</ion-label><br />
</div>
</div>
<div style="text-align: end;">
<div *ngIf="msg.file && msg.delate == true">
Apagou a mensagem
<div *ngIf="message.messageStatus != 'send'" style="font-size: .6875rem;">A enviar</div>
<div *ngIf="message.messageStatus == 'send'" style="font-size: .6875rem;">Enviado</div>
</div>
<!-- Emoji Picker -->
<div *ngIf="selectedMessage === message" class="emoji-picker" [ngStyle]="{'bottom': '0', 'right': '0'}">
<span *ngFor="let emoji of emojis" (click)="addReaction(message, emoji)" class="emoji-icon">
{{ emoji }}
</span>
</div>
</div>
<!-- current emoji -->
<div>
<span *ngFor="let reaction of message.reactions" class="emoji-icon">
{{ reaction.reaction }}
</span>
</div>
</div>
</div>
<!--
<ion-fab horizontal="end" vertical="bottom" slot="fixed">
<ion-fab-button *ngIf="scrollToBottomBtn" (click)="scrollToBottomClicked()" color="light" size="small">
<ion-fab-button *ngIf="scrollToBottomBtn" color="light" size="small">
<ion-icon name="chevron-down"></ion-icon>
</ion-fab-button>
</ion-fab> -->
<ion-fab horizontal="start" vertical="bottom" slot="fixed">
<div #array>
</div>
</ion-fab>
<!-- <div *ngIf="userTyping$?.typingList?.typingList">
<div
*ngFor="let message of userTyping$.typingList.typingList " class="messages-list-item-wrapper"
>
{{ message.userName }}
</div>
</div>
-->
<!-- <div *ngIf="userTyping$" class="header-bottom-contacts" >
<div *ngFor="let typing of userTyping$; let i = index">
{{ typing.userName }}<div *ngIf="i < userTyping$.length - 1">, </div>
</div>
</div>
-->
</ion-content>
<ion-footer *ngIf="ChatSystemService.getDmRoom(roomId)" (click)="ChatSystemService.getDmRoom(roomId).sendReadMessage()">
<ion-footer>
<div class="typing" *ngIf="ChatSystemService.getDmRoom(roomId).otherUserType == true">
<!-- <div class="typing" *ngIf="ChatSystemService.getDmRoom(roomId).otherUserType == true">
<ngx-letters-avatar [avatarName]="ChatSystemService.getGroupRoom(roomId).name" [width]="30" [circular]="true"
fontFamily="Roboto"></ngx-letters-avatar>
está a escrever ...
</div>
</div> -->
<div class="width-100 pl-20 pr-20">
<span *ngIf="!lastAudioRecorded">{{durationDisplay}}</span>
<audio [src]="audioRecorded" class="d-flex width-100 mt-10 mb-10" *ngIf="lastAudioRecorded" controls
@@ -277,8 +160,8 @@
<div *ngIf="!recording && !lastAudioRecorded" class="type-message">
<ion-textarea *ngIf="allowTyping" autocomplete="on" autocorrect="on" spellcheck="true" clearOnEdit="true"
placeholder="Escrever uma mensagem" auto-grow class="message-input" rows="1"
[(ngModel)]="ChatSystemService.getDmRoom(roomId).message"
(ionChange)="ChatSystemService.getDmRoom(roomId).sendTyping()"></ion-textarea>
[(ngModel)]="textField"
(ionChange)="sendTyping()"></ion-textarea>
</div>
<div *ngIf="recording" class="d-flex align-items-center justify-content-center">
<button (click)="stopRecording()" class="btn-no-color d-flex align-items-center justify-content-center">
@@ -287,21 +170,21 @@
</div>
</div>
<div>
<button #recordbtn *ngIf="!ChatSystemService.getDmRoom(roomId).message && !lastAudioRecorded"
<button #recordbtn *ngIf="!textField && !lastAudioRecorded"
(click)="startRecording()" class="btn-no-color">
<ion-icon *ngIf="ThemeService.currentTheme == 'default' " class="chat-icon-send"
src="assets/icon/theme/default/icons-chat-record-audio.svg"></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'gov' " class="chat-icon-send"
src="assets/icon/theme/gov/icons-chat-record-audio.svg"></ion-icon>
</button>
<button *ngIf="ChatSystemService.getDmRoom(roomId).message" class="btn-no-color" (click)="sendMessage()"
<button *ngIf="textField" class="btn-no-color" (click)="sendMessage()"
class="btn-no-color">
<ion-icon *ngIf="ThemeService.currentTheme == 'default' " class="chat-icon-send"
src="assets/icon/theme/gov/icons-chat-send.svg"></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'gov' " class="chat-icon-send"
src="assets/icon/theme/gov/icons-chat-send.svg"></ion-icon>
</button>
<button *ngIf="!ChatSystemService.getDmRoom(roomId).message && lastAudioRecorded"
<button *ngIf="!textField && lastAudioRecorded"
(click)="sendAudio(lastAudioRecorded)" class="btn-no-color">
<ion-icon *ngIf="ThemeService.currentTheme == 'default' " class="chat-icon-send"
src="assets/icon/theme/gov/icons-chat-send.svg"></ion-icon>
@@ -311,4 +194,4 @@
</div>
</div>
</ion-footer>
</ion-footer>
@@ -416,3 +416,81 @@ button::-moz-focus-inner {
width: 111px !important;
text-align: center;
}
.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;
}
.messages {
font-size: rem(13);
font-family: Roboto;
overflow: auto;
//set scroll do bottom
// position: absolute;
// top: 0;
// left: 0;
// overflow-x: hidden;
// overflow-y: auto;
// width: 100%;
// height: 100%;
// word-wrap: break-word;
// -webkit-overflow-scrolling: touch;
.other-message,
.my-message {
.message-container {
padding: 15px 20px;
margin: 10px 20px 10px 75px;
background: var(--chat-incoming-msg-color);
border-radius: 10px;
}
}
.other-message {
display: flex;
/* justify-content: flex-end; */
align-items: start;
flex-direction: column;
// float: left;
// Styles for incoming messages from other users
justify-content: flex-start;
.message-container {
padding: 15px 20px;
margin: 10px 75px 10px 20px;
background: #ebebeb;
border-radius: 10px;
}
}
}
.my-message {
display: flex;
/* justify-content: flex-end; */
align-items: end;
flex-direction: column;
}
+126 -15
View File
@@ -37,6 +37,21 @@ import { RochetChatConnectorService } from 'src/app/services/chat/rochet-chat-co
import { FileValidatorService } from "src/app/services/file/file-validator.service"
import { sanitize } from "sanitize-filename-ts";
import { FilePicker } from '@capawesome/capacitor-file-picker';
//======
import { Observable as DexieObservable } from 'Dexie';
import { Observable, Subscription } from 'rxjs';
import { MessageRepositoryService } from 'src/app/module/chat/data/repository/message-respository.service'
import { RoomRepositoryService } from 'src/app/module/chat/data/repository/room-repository.service'
import { MessageTable } from 'src/app/module/chat/infra/database/dexie/schema/message';
import { MessageInputDTO } from 'src/app/module/chat/data/dto/message/messageInputDtO';
import { RoomListItemOutPutDTO } from 'src/app/module/chat/data/dto/room/roomListOutputDTO';
import { UserTypingServiceRepository } from 'src/app/module/chat/data/repository/user-typing-repository.service';
import { ChatServiceService } from 'src/app/module/chat/domain/chat-service.service';
import { EditMessagePage } from 'src/app/modals/edit-message/edit-message.page';
import { tap } from 'rxjs/operators';
import { MessageEntity } from 'src/app/module/chat/domain/entity/message';
import { MemberTable } from 'src/app/module/chat/infra/database/dexie/schema/members';
import { TypingTable } from 'src/app/module/chat/infra/database/dexie/schema/typing';
@@ -104,6 +119,18 @@ export class MessagesPage implements OnInit, AfterViewInit, OnDestroy {
audioPermissionStatus: 'granted' | 'denied' | 'prompt' | null = null
sessionStore = SessionStore
roomData$: DexieObservable<RoomListItemOutPutDTO | undefined>
roomStatus$: DexieObservable<Boolean >
roomMessage$: DexieObservable<MessageTable[]>
roomMembers$: DexieObservable<MemberTable[] | undefined>
//userTyping$: DexieObservable<TypingTable[] | undefined>
userTyping$: TypingTable[] | undefined
newMessagesStream!: Subscription
selectedMessage: any = null;
emojis: string[] = ['😊', '😂', '❤️', '👍', '😢']; // Add more emojis as needed
textField = ''
constructor(
public popoverController: PopoverController,
private modalController: ModalController,
@@ -124,31 +151,54 @@ export class MessagesPage implements OnInit, AfterViewInit, OnDestroy {
private router: Router,
public RochetChatConnectorService: RochetChatConnectorService,
private FileValidatorService: FileValidatorService,
///
private roomRepositoryService: RoomRepositoryService,
private messageRepositoryService: MessageRepositoryService,
private userTypingServiceRepository: UserTypingServiceRepository,
private chatServiceService: ChatServiceService
) {
try {
this.loggedUser = SessionStore.user.ChatData['data'];
this.roomId = this.navParams.get('roomId');
this.roomId = this.navParams.get('roomId');
window.onresize = (event) => {
if (window.innerWidth > 701) {
this.modalController.dismiss();
}
}
this.roomData$ = this.roomRepositoryService.getItemByIdLive(this.roomId)
this.roomMessage$ = this.messageRepositoryService.getItemsLive(this.roomId)
this.ChatSystemService.getDmRoom(this.roomId).loadHistory({})
this.ChatSystemService.getDmRoom(this.roomId).scrollDown = this.scrollToBottomClicked
this.ChatSystemService.openRoom(this.roomId)
this.roomMembers$ = this.roomRepositoryService.getRoomMemberByIdLive(this.roomId) as any
this.roomStatus$ = this.roomRepositoryService.getRoomStatus(this.roomId)
this.roomRepositoryService.getRoomById(this.roomId)
this.messageRepositoryService.listAllMessagesByRoomId(this.roomId)
this.userTypingServiceRepository.getUserTypingLive().subscribe((e) => {
const arrayNames = e.map(e => e.userName)
this.userTyping$ = e as any
const uniqueArray = [...new Set(arrayNames)];
//(this.myInputRef.nativeElement as HTMLDivElement).innerHTML = '::'+ uniqueArray
})
this.newMessagesStream?.unsubscribe()
this.newMessagesStream = this.messageRepositoryService.subscribeToNewMessages(this.roomId).subscribe((e) => {
setTimeout(() => {
this.scrollToBottomClicked()
}, 150)
} catch (error) {
//alert(error)
}
}, 200)
setTimeout(() => {
this.scrollToBottomClicked()
}, 500)
})
//this.userTyping$ = this.userTypingMemoryDataSource.select(state => state) as any
// let a = this.userTypingMemoryDataSource.select(state => state).subscribe((e) => {
// this.userTyping$ = e as any
// })
}
@@ -1336,6 +1386,67 @@ export class MessagesPage implements OnInit, AfterViewInit, OnDestroy {
return blob;
}
messageDelete({messageId}) {
// this.messageRepositoryService.sendMessageDelete()
console.log('messageId', messageId)
this.chatServiceService.messageDelete({
messageId: messageId,
roomId: this.roomId,
})
}
async editMessage(message: any) {
const modal = await this.modalController.create({
component: EditMessagePage,
cssClass: '',
componentProps: {
message: message.message,
roomId: this.roomId,
}
});
modal.present()
return modal.onDidDismiss().then((res) => {
this.chatServiceService.updateMessage({
memberId: SessionStore.user.UserId,
message: res.data.message,
messageId: message.id,
requestId: '',
roomId: this.roomId
})
});
}
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.id,
roomId: this.roomId,
reaction: emoji,
requestId: ''
})
}
sendTyping() {
this.userTypingServiceRepository.addUserTyping(this.roomId)
}
}
@@ -20,8 +20,8 @@ export function APIReturn(schema: z.ZodTypeAny, context: string) {
if (error instanceof ZodError) {
// If validation fails, throw an error with the details
//
ColoredLoggerService.error(error.errors, 'API unexpected data structure '+ context, schema._def.description)
ColoredLoggerService.error(error.errors, 'API unexpected data structure '+ context)
console.error(result.value)
// Capture the Zod validation error with additional context
Sentry.withScope((scope) => {
scope.setTag('APIReturn', 'user');
@@ -64,6 +64,7 @@ export function SafeValidateSchema(schema: Schema, context: string) {
tracing?.setAttribute?.('map.error.schema-'+i, JSON.stringify(schema))
}
ColoredLoggerService.error(e.errors, 'socket unexpected data structure '+ context, schema._def.description)
console.error(args[0])
}
return originalMethod.apply(this, args);
@@ -58,7 +58,7 @@
<div class="message-item-options d-flex justify-content-end">
<fa-icon [matMenuTriggerFor]="beforeMenu" icon="chevron-down" class="message-options-icon cursor-pointer"></fa-icon>
<mat-menu #beforeMenu="matMenu" xPosition="before">
<button (click)="messageDelete({messageId: message.id })" class="menuButton">Apagar mensagem</button>
<button (click)="messageDelete(message)" class="menuButton">Apagar mensagem</button>
<button (click)="editMessage(message)" class="menuButton">Editar mensagem</button>
<button (click)="toggleEmojiPicker(message)" class="menuButton">Reagir mensagem</button>
</mat-menu>
@@ -993,7 +993,6 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
messageDelete({messageId}) {
// this.messageRepositoryService.sendMessageDelete()
this.chatServiceService.messageDelete({
messageId: messageId,
roomId: this.roomId,