allow to send files to chat

This commit is contained in:
Peter Maquiran
2024-08-09 12:36:51 +01:00
parent 28bc141d38
commit c35f1e1f44
11 changed files with 264 additions and 138 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ export class AppComponent {
if(this.platform.is('ios')){
this.screenOrientation.lock('portrait')
} else {
window.screen.orientation.lock('portrait');
(window.screen.orientation as any).lock('portrait');
}
}
});
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { CameraService } from './camera.service';
describe('CameraService', () => {
let service: CameraService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CameraService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+34
View File
@@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { err, ok } from 'neverthrow';
type takePictureParams = {
quality?: number
cameraResultType: CameraResultType
}
@Injectable({
providedIn: 'root'
})
export class CameraService {
constructor() { }
async takePicture({quality = 90, cameraResultType }: takePictureParams) {
try {
const file = await Camera.getPhoto({
quality: quality,
// allowEditing: true,
resultType: cameraResultType,
source: CameraSource.Camera
});
return ok(file)
} catch (e) {
return err(e)
}
}
}
@@ -1,6 +1,13 @@
import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { err, ok, Result } from 'neverthrow';
import { FileType } from 'src/app/models/fileType';
type PickPictureParams = {
quality?: number,
cameraResultType?: CameraResultType
}
@Injectable({
providedIn: 'root'
})
@@ -8,7 +15,7 @@ export class FilePickerWebService {
constructor() { }
getFileFromDevice(types: typeof FileType[]): Promise<File> {
getFileFromDevice(types: typeof FileType[]): Promise<Result<File, any>> {
let input = document.createElement('input');
input.type = 'file';
input.accept = types.join(', ')
@@ -18,9 +25,24 @@ export class FilePickerWebService {
return new Promise((resolve, reject)=>{
input.onchange = async () => {
const file = Array.from(input.files)
resolve(file[0] as File);
resolve(ok(file[0] as File));
};
})
}
async getPicture({quality = 90, cameraResultType =CameraResultType.DataUrl }: PickPictureParams) {
try {
const file = await Camera.getPhoto({
quality: quality,
// allowEditing: true,
resultType: cameraResultType,
source: CameraSource.Photos
});
return ok(file)
} catch (e) {
return err(e)
}
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ export class MessageEntity implements Message {
attachments: {
fileType: MessageAttachmentFileType,
source: MessageAttachmentSource,
file: string,
file?: string,
fileName: string,
applicationId?: string,
docId?: string
@@ -162,7 +162,7 @@
<ion-fab-button title="Adicionar Documento" (click)="addFile()" color="light">
<ion-icon name="document"></ion-icon>
</ion-fab-button>
<ion-fab-button title="Anexar Fotografia" (click)="addImage()" color="light">
<ion-fab-button title="Anexar Fotografia" (click)="pickPicture()" color="light">
<ion-icon name="image"></ion-icon>
</ion-fab-button>
<ion-fab-button title="Tirar Fotografia" (click)="takePictureMobile()" color="light">
+101 -131
View File
@@ -48,7 +48,10 @@ import { MemberTable } from 'src/app/module/chat/infra/database/dexie/schema/mem
import { TypingTable } from 'src/app/module/chat/infra/database/dexie/schema/typing';
import { MessageAttachmentFileType, MessageAttachmentSource } from 'src/app/module/chat/data/dto/message/messageOutputDTO';
import { JSFileToBase64 } from 'src/app/utils/ToBase64';
import { CameraService } from 'src/app/infra/camera/camera.service'
import { compressImageBase64 } from '../../../utils/imageCompressore';
import { FilePickerWebService } from 'src/app/infra/file-picker/web/file-picker-web.service'
import { allowedDocExtension } from 'src/app/utils/allowedDocExtension';
const IMAGE_DIR = 'stored-images';
@Component({
@@ -158,6 +161,8 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
private messageRepositoryService: MessageRepositoryService,
private userTypingServiceRepository: UserTypingServiceRepository,
private chatServiceService: ChatServiceService,
private CameraService: CameraService,
private FilePickerWebService: FilePickerWebService
) {
// update
this.checkAudioPermission()
@@ -679,17 +684,6 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
await modal.present();
}
openSendMessageOptions(ev?: any) {
if (window.innerWidth < 701) {
this.openChatOptions(ev);
}
else {
this._openChatOptions();
}
}
async openChatOptions(ev: any) {
const popover = await this.popoverController.create({
component: ChatOptionsPopoverPage,
@@ -798,57 +792,50 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
async takePictureMobile() {
const picture = await this.CameraService.takePicture({
cameraResultType: CameraResultType.DataUrl,
quality: 90
})
const roomId = this.roomId
if(picture.isOk()) {
const file = picture.value
const file = await Camera.getPhoto({
quality: 90,
// allowEditing: true,
resultType: CameraResultType.Base64,
source: CameraSource.Camera
});
console.log('Selected: ', file)
var base64 = 'data:image/jpeg;base64,' + file.base64String
const compressedImage = await this.compressImageBase64(
base64,
800, // maxWidth
800, // maxHeight
0.9 // quality
).then((picture) => {
const compressedImage = await compressImageBase64(
file.dataUrl,
800, // maxWidth
800, // maxHeight
0.9 // quality
)
base64 = picture
});
if(compressedImage.isOk()) {
const message = new MessageEntity();
message.roomId = this.roomId
const blob = this.dataURItoBlob(base64)
message.sender = {
userPhoto: '',
wxeMail: SessionStore.user.Email,
wxFullName: SessionStore.user.FullName,
wxUserId: SessionStore.user.UserId
}
const formData = new FormData();
formData.append("blobFile", blob);
message.attachments = [{
file: compressedImage.value,
fileName: "foto",
source: MessageAttachmentSource.Device,
fileType: MessageAttachmentFileType.Image
}]
// this.ChatSystemService.getDmRoom(roomId).send({
// file: {
// "type": "application/img",
// "guid": '',
// },
// temporaryData: formData,
// attachments: [{
// "title": file.path,
// // "image_url": "",
// //"image_url": 'data:image/jpeg;base64,' + file.base64String,
// "text": "description",
// "title_link_download": false,
// }],
// attachmentsModelData: {
// fileBase64: base64,
// }
// })
this.chatServiceService.sendMessage(message)
}
}
}
async takePicture() {
const roomId = this.roomId
const file = await Camera.getPhoto({
quality: 90,
// allowEditing: true,
@@ -885,10 +872,6 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
}
async addImage() {
this.addFileToChatMobile(['image/apng', 'image/jpeg', 'image/png'])
}
async addFile() {
this.addFileToChat(['.doc', '.docx', '.pdf'], MessageAttachmentFileType.Doc)
}
@@ -907,29 +890,32 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
modal.onDidDismiss().then(async res => {
const data = res.data;
const roomId = this.roomId
if (data.selected) {
// this.ChatSystemService.getDmRoom(roomId).send({
// file: {
// "name": res.data.selected.Assunto,
// "type": "application/webtrix",
// "ApplicationId": res.data.selected.ApplicationType,
// "DocId": res.data.selected.Id,
// "Assunto": res.data.selected.Assunto,
// },
// temporaryData: res,
// attachments: [{
// "title": res.data.selected.Assunto,
// "description": res.data.selected.DocTypeDesc,
// "title_link_download": true,
// "type": "webtrix",
// "text": res.data.selected.DocTypeDesc,
// "thumb_url": "https://static.ichimura.ed.jp/uploads/2017/10/pdf-icon.png",
// }],
// })
// "title": res.data.selected.Assunto,
// "description": res.data.selected.DocTypeDesc,
const message = new MessageEntity();
message.message = this.textField
message.roomId = this.roomId
message.sender = {
userPhoto: '',
wxeMail: SessionStore.user.Email,
wxFullName: SessionStore.user.FullName,
wxUserId: SessionStore.user.UserId
}
message.attachments = [{
fileName: res.data.selected.Assunto,
source: MessageAttachmentSource.Webtrix,
fileType: MessageAttachmentFileType.Doc,
applicationId: res.data.selected.ApplicationType,
docId: res.data.selected.Id,
}]
this.chatServiceService.sendMessage(message)
this.textField = ''
}
});
@@ -937,58 +923,48 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
await modal.present();
}
async addFileToChatMobile(types: typeof FileType[]) {
const roomId = this.roomId
async pickPicture() {
const file = await Camera.getPhoto({
quality: 90,
// allowEditing: true,
resultType: CameraResultType.Base64,
source: CameraSource.Photos
});
const file = await this.FilePickerWebService.getPicture({
cameraResultType: CameraResultType.Base64
})
//const imageData = await this.fileToBase64Service.convert(file)
//
console.log('Selected: ', file)
var base64 = 'data:image/jpeg;base64,' + file.base64String
if (file.format == "jpeg" || file.format == "png" || file.format == "gif") {
if(file.isOk()) {
const compressedImage = await this.compressImageBase64(
base64,
800, // maxWidth
800, // maxHeight
0.9 // quality
).then((picture) => {
var base64 = 'data:image/jpeg;base64,' + file.value.base64String
if (file.value.format == "jpeg" || file.value.format == "png" || file.value.format == "gif") {
base64 = picture
});
const compressedImage = await compressImageBase64(
base64,
800, // maxWidth
800, // maxHeight
0.9 // quality
)
const response = await fetch(base64);
const blob = await response.blob();
if(compressedImage.isOk()) {
console.log(base64)
const message = new MessageEntity();
message.roomId = this.roomId
const formData = new FormData();
formData.append("blobFile", blob);
message.sender = {
userPhoto: '',
wxeMail: SessionStore.user.Email,
wxFullName: SessionStore.user.FullName,
wxUserId: SessionStore.user.UserId
}
// this.ChatSystemService.getDmRoom(roomId).send({
// file: {
// "type": "application/img",
// "guid": ''
// },
// temporaryData: formData,
// attachments: [{
// "title": file.path,
// //"image_url": 'data:image/jpeg;base64,' + file.base64String,
// "text": "description",
// "title_link_download": false,
// }],
// attachmentsModelData: {
// fileBase64: base64,
// }
// })
message.attachments = [{
file: compressedImage.value,
fileName: "foto",
source: MessageAttachmentSource.Device,
fileType: MessageAttachmentFileType.Image
}]
this.chatServiceService.sendMessage(message)
}
}
}
}
@@ -1003,20 +979,16 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
async addFileToChat(types: typeof FileType[], attachmentFileType:MessageAttachmentFileType) {
const file = await this.fileService.getFileFromDevice(types);
const file = await this.FilePickerWebService.getFileFromDevice(types);
if(file.isOk()) {
if (file.type == 'application/pdf' || file.type == 'application/doc' || file.type == 'application/docx' ||
file.type == 'application/xls' || file.type == 'application/xlsx' || file.type == 'application/ppt' ||
file.type == 'application/pptx' || file.type == 'application/txt') {
const fileExtension = await allowedDocExtension(file.value.type)
console.log('FILE rigth?', file)
if(fileExtension.isOk()) {
const fileName = file.name
console.log('FILE rigth?', file)
const FilenameValidation = this.FileValidatorService.fileNameValidation(fileName)
if (FilenameValidation.isOk) {
let fileBase64 = await JSFileToBase64(file);
let fileBase64 = await JSFileToBase64(file.value);
if(fileBase64.isOk()) {
@@ -1032,7 +1004,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
message.attachments = [{
file: fileBase64.value,
fileName: file.name,
fileName: file.value.name,
source: MessageAttachmentSource.Device,
fileType: MessageAttachmentFileType.Doc
}]
@@ -1043,9 +1015,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
} else {
this.toastService._badRequest("Ficheiro inválido")
}
}
}
_getBase64(file) {
@@ -1151,7 +1121,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
}
else if (res['data'] == 'add-picture') {
this.addImage()
this.pickPicture()
}
else if (res['data'] == 'add-document') {
@@ -1371,7 +1341,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
async compressImageBase64(base64String: string, maxWidth: number, maxHeight: number, quality: number): Promise<string> {
return new Promise((resolve, reject) => {
const image = new (window as any).Image();
const image = new Image();
image.src = base64String;
image.onload = async () => {
+12
View File
@@ -0,0 +1,12 @@
import { err, ok } from "neverthrow"
export function allowedDocExtension(type: string) {
if (type == 'application/pdf' || type == 'application/doc' || type == 'application/docx' ||
type == 'application/xls' || type == 'application/xlsx' || type == 'application/ppt' ||
type == 'application/pptx' || type == 'application/txt') {
return ok(true)
} else {
return err(false)
}
}
+72
View File
@@ -0,0 +1,72 @@
import { err, ok, Result } from "neverthrow";
/**
* Compresses an image represented as a Base64 string.
*
* This function resizes the image to fit within the specified maximum width and height while maintaining the aspect ratio.
* The image is then compressed to a JPEG format with the given quality level.
*
* @param base64String - The Base64 string of the image to be compressed.
* @param maxWidth - The maximum width of the compressed image. The aspect ratio is preserved.
* @param maxHeight - The maximum height of the compressed image. The aspect ratio is preserved.
* @param quality - The quality of the compressed image, ranging from 0 to 1, where 1 is the best quality.
*
* @returns A Promise that resolves to a `Result` containing either:
* - `ok` with the compressed image as a Base64 string, or
* - `err` with an error if the image fails to load or compress.
*
* @example
* ```typescript
* compressImageBase64('data:image/png;base64,...', 800, 600, 0.8)
* .then(result => {
* if (result.isOk()) {
* console.log('Compressed image:', result.value);
* } else {
* console.error('Error compressing image:', result.error);
* }
* });
* ```
*/
export function compressImageBase64(base64String: string, maxWidth: number, maxHeight: number, quality: number): Promise<Result<string, any>> {
return new Promise((resolve, reject) => {
const image = new Image();
image.src = base64String;
image.onload = async () => {
const canvas = document.createElement('canvas');
let newWidth = image.width;
let newHeight = image.height;
if (newWidth > maxWidth) {
newHeight *= maxWidth / newWidth;
newWidth = maxWidth;
}
if (newHeight > maxHeight) {
newWidth *= maxHeight / newHeight;
newHeight = maxHeight;
}
canvas.width = newWidth;
canvas.height = newHeight;
const context = canvas.getContext('2d');
context?.drawImage(image, 0, 0, newWidth, newHeight);
const compressedBase64 = canvas.toDataURL('image/jpeg', quality);
// Calculate the compression percentage
const originalSize = base64String.length;
const compressedSize = compressedBase64.length;
const compressionPercentage = ((originalSize - compressedSize) / originalSize) * 100;
console.log(`Compression achieved: ${compressionPercentage.toFixed(2)}%`);
resolve(ok(compressedBase64));
};
image.onerror = (error) => {
resolve(err(error));
};
});
}