-
Notifications
You must be signed in to change notification settings - Fork 15
feat: Change the old file picker with new native capacitor plugin #3846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,9 @@ | |
| /* eslint-disable complexity */ | ||
| import { TitleCasePipe } from '@angular/common'; | ||
| import { Component, ElementRef, EventEmitter, HostListener, OnInit, ViewChild } from '@angular/core'; | ||
| import { FilePicker } from '@capawesome/capacitor-file-picker'; | ||
| import { Camera, CameraResultType, CameraSource } from '@capacitor/camera'; | ||
| import { Filesystem } from '@capacitor/filesystem'; | ||
| import { | ||
| AbstractControl, | ||
| UntypedFormArray, | ||
|
|
@@ -199,8 +202,6 @@ type FormValue = { | |
| export class AddEditExpensePage implements OnInit { | ||
| @ViewChild('formContainer') formContainer: ElementRef<HTMLFormElement>; | ||
|
|
||
| @ViewChild('fileUpload', { static: false }) fileUpload: ElementRef<HTMLInputElement>; | ||
|
|
||
| @ViewChild('projectDependentFieldsRef') projectDependentFieldsRef: DependentFieldsComponent; | ||
|
|
||
| @ViewChild('costCenterDependentFieldsRef') costCenterDependentFieldsRef: DependentFieldsComponent; | ||
|
|
@@ -4786,112 +4787,110 @@ export class AddEditExpensePage implements OnInit { | |
| } | ||
| } | ||
|
|
||
| async uploadFileCallback(file: File): Promise<void> { | ||
| let fileData: { type: string; dataUrl: string | ArrayBuffer; actionSource: string }; | ||
| if (file) { | ||
| if (file.size < MAX_FILE_SIZE) { | ||
| const fileRead$ = from(this.fileService.readFile(file)); | ||
| const delayedLoader$ = timer(300).pipe( | ||
| switchMap(() => from(this.loaderService.showLoader('Please wait...', 5000))), | ||
| switchMap(() => fileRead$), // switch to fileRead$ after showing loader | ||
| ); | ||
| // Use race to show loader only if fileRead$ takes more than 300ms. | ||
| fileRead$ | ||
| .pipe( | ||
| raceWith(delayedLoader$), | ||
| map((dataUrl) => { | ||
| fileData = { | ||
| type: file.type, | ||
| dataUrl, | ||
| actionSource: 'gallery_upload', | ||
| }; | ||
| this.attachReceipts(fileData); | ||
| this.trackingService.addAttachment({ type: file.type }); | ||
| }), | ||
| finalize(() => this.loaderService.hideLoader()), | ||
| ) | ||
| .subscribe(); | ||
| async addAttachments(event: Event): Promise<void> { | ||
| event.stopPropagation(); | ||
| const popup = await this.popoverController.create({ | ||
| component: CameraOptionsPopupComponent, | ||
| cssClass: 'camera-options-popover', | ||
| }); | ||
|
|
||
| await popup.present(); | ||
|
|
||
| const { data } = (await popup.onWillDismiss()) as { | ||
| data: { | ||
| option?: string; | ||
| type: string; | ||
| dataUrl: string; | ||
| actionSource?: string; | ||
| }; | ||
| }; | ||
|
|
||
| if (data && data.option) { | ||
| if (data.option === 'camera') { | ||
| this.openCamera(); | ||
| } else { | ||
| this.showSizeLimitExceededPopover(MAX_FILE_SIZE); | ||
| this.openGallery(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| async onChangeCallback(nativeElement: HTMLInputElement): Promise<void> { | ||
| const file = nativeElement.files[0]; | ||
| this.uploadFileCallback(file); | ||
| } | ||
| async openCamera(): Promise<void> { | ||
| try { | ||
| const image = await Camera.getPhoto({ | ||
| quality: 90, | ||
| allowEditing: false, | ||
| resultType: CameraResultType.DataUrl, | ||
| source: CameraSource.Camera, | ||
| }); | ||
|
|
||
| async addAttachments(event: Event): Promise<void> { | ||
| event.stopPropagation(); | ||
| if (image && image.dataUrl) { | ||
| const receiptDetails = { | ||
| type: this.fileService.getImageTypeFromDataUrl(image.dataUrl), | ||
| dataUrl: image.dataUrl, | ||
| actionSource: 'camera', | ||
| }; | ||
| this.attachReceipts(receiptDetails); | ||
| this.showAddedToExpenseToast(); | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| } catch (error) { | ||
| this.showSnackBarToast({ message: 'Error opening camera' }, 'failure', ['msb-error-with-camera-icon']); | ||
| } | ||
| } | ||
|
|
||
|
Comment on lines
+4817
to
4840
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Kabali da! Camera handling is super, but error tracking needs more power! The camera error handling catches the error but doesn't log it. In production, you'll want to know what went wrong. } catch (error) {
+ console.error('Camera error:', error);
+ this.trackingService.captureException(error);
this.showSnackBarToast({ message: 'Error opening camera' }, 'failure', ['msb-error-with-camera-icon']);
}🤖 Prompt for AI Agents |
||
| if (this.platform.is('ios')) { | ||
| const nativeElement = this.fileUpload.nativeElement; | ||
| // eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
| nativeElement.onchange = async () => { | ||
| this.onChangeCallback(nativeElement); | ||
| }; | ||
| nativeElement.click(); | ||
| } else { | ||
| const popup = await this.popoverController.create({ | ||
| component: CameraOptionsPopupComponent, | ||
| cssClass: 'camera-options-popover', | ||
| componentProps: { | ||
| mode: this.mode, | ||
| }, | ||
| async openGallery(): Promise<void> { | ||
| try { | ||
| const result = await FilePicker.pickFiles({ | ||
| types: ['image/jpeg', 'image/png', 'application/pdf', 'image/heic'], | ||
| readData: true, | ||
| }); | ||
|
|
||
| await popup.present(); | ||
|
|
||
| let { data: receiptDetails } = (await popup.onWillDismiss()) as { | ||
| data: { | ||
| option?: string; | ||
| type: string; | ||
| dataUrl: string; | ||
| actionSource?: string; | ||
| }; | ||
| }; | ||
| if (result.files.length > 0) { | ||
| const file = result.files[0]; | ||
|
|
||
| if (receiptDetails && receiptDetails.option === 'camera') { | ||
| const captureReceiptModal = await this.modalController.create({ | ||
| component: CaptureReceiptComponent, | ||
| componentProps: { | ||
| isModal: true, | ||
| allowGalleryUploads: false, | ||
| allowBulkFyle: false, | ||
| }, | ||
| cssClass: 'hide-modal', | ||
| }); | ||
| if (file.size > MAX_FILE_SIZE) { | ||
| this.showSizeLimitExceededPopover(MAX_FILE_SIZE); | ||
| return; | ||
| } | ||
|
|
||
| await captureReceiptModal.present(); | ||
| this.isCameraPreviewStarted = true; | ||
| from(this.loaderService.showLoader('Please wait...', 5000)).subscribe(async () => { | ||
| let dataUrl = `data:${file.mimeType};base64,${file.data}`; | ||
|
|
||
| const { data } = (await captureReceiptModal.onWillDismiss()) as { | ||
| data: { | ||
| dataUrl: string; | ||
| }; | ||
| }; | ||
| this.isCameraPreviewStarted = false; | ||
| if (file.mimeType === 'image/heic') { | ||
| const conversionResult = await FilePicker.convertHeicToJpeg({ | ||
| path: file.path, | ||
| }); | ||
| const jpegFile = await Filesystem.readFile({ | ||
| path: conversionResult.path, | ||
| }); | ||
| dataUrl = `data:image/jpeg;base64,${jpegFile.data}`; | ||
| } | ||
|
|
||
| if (data && data.dataUrl) { | ||
| receiptDetails = { | ||
| type: this.fileService.getImageTypeFromDataUrl(data.dataUrl), | ||
| dataUrl: data.dataUrl, | ||
| actionSource: 'camera', | ||
| const receiptDetails = { | ||
| type: file.mimeType, | ||
| dataUrl: dataUrl, | ||
| actionSource: 'gallery_upload', | ||
| }; | ||
| } | ||
| } | ||
| if (receiptDetails && receiptDetails.dataUrl) { | ||
| this.attachReceipts(receiptDetails as { type: string; dataUrl: string }); | ||
| const message = 'Receipt added to Expense successfully'; | ||
| this.showSnackBarToast({ message }, 'success', ['msb-success-with-camera-icon']); | ||
| this.showReceiptMandatoryError = false; | ||
|
|
||
| this.trackingService.showToastMessage({ ToastContent: message }); | ||
| this.attachReceipts(receiptDetails); | ||
| this.trackingService.addAttachment({ type: file.mimeType }); | ||
| this.showAddedToExpenseToast(); | ||
| this.loaderService.hideLoader(); | ||
| }); | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| } catch (error) { | ||
| this.showSnackBarToast({ message: 'Error opening gallery' }, 'failure', ['msb-error-with-camera-icon']); | ||
|
Comment on lines
+4882
to
+4883
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Gallery error also needs the same punch as camera error, machi! Similar to the camera error, the gallery error should also be logged for debugging. } catch (error) {
+ console.error('Gallery error:', error);
+ this.trackingService.captureException(error);
this.showSnackBarToast({ message: 'Error opening gallery' }, 'failure', ['msb-error-with-camera-icon']);
}🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
| showAddedToExpenseToast(): void { | ||
| const message = 'Receipt added to Expense successfully'; | ||
| this.showSnackBarToast({ message }, 'success', ['msb-success-with-camera-icon']); | ||
| this.showReceiptMandatoryError = false; | ||
| this.trackingService.showToastMessage({ ToastContent: message }); | ||
| } | ||
|
|
||
| getReceiptExtension(name: string): string | null { | ||
| let res: string = null; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Mind it! This popup flow is smooth as my stunts, but needs one small touch!
The
addAttachmentsmethod is well-structured. However, when the popup is dismissed without selecting an option, the data could be undefined. Consider adding a null check.🤖 Prompt for AI Agents