MOBILE-3761 ios: Allow open files with external app in iOS
This commit is contained in:
		
							parent
							
								
									f3890cd916
								
							
						
					
					
						commit
						778ad6d803
					
				| @ -1984,6 +1984,7 @@ | ||||
|   "core.openmodinbrowser": "local_moodlemobileapp", | ||||
|   "core.opensecurityquestion": "local_moodlemobileapp", | ||||
|   "core.opensettings": "local_moodlemobileapp", | ||||
|   "core.openwith": "local_moodlemobileapp", | ||||
|   "core.othergroups": "group", | ||||
|   "core.pagea": "moodle", | ||||
|   "core.parentlanguage": "langconfig", | ||||
|  | ||||
| @ -45,11 +45,16 @@ | ||||
|         <core-format-text [text]="contentText" [filter]="false"></core-format-text> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="ion-padding" *ngIf="mode == 'external'"> | ||||
|         <ion-button expand="block" (click)="open()"> | ||||
|     <ng-container *ngIf="mode == 'external'"> | ||||
|         <ion-button expand="block" class="ion-margin" (click)="open(false)"> | ||||
|             <ion-icon name="far-file" slot="start" aria-hidden="true"></ion-icon> | ||||
|             {{ 'addon.mod_resource.openthefile' | translate }} | ||||
|         </ion-button> | ||||
|     </div> | ||||
| 
 | ||||
|         <ion-button *ngIf="isIOS" expand="block" class="ion-margin" (click)="open(true)"> | ||||
|             <ion-icon name="far-share-square" slot="start" aria-hidden="true"></ion-icon> | ||||
|             {{ 'core.openwith' | translate }} | ||||
|         </ion-button> | ||||
|     </ng-container> | ||||
| 
 | ||||
| </core-loading> | ||||
|  | ||||
| @ -20,6 +20,7 @@ import { | ||||
| import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | ||||
| import { CoreCourse, CoreCourseWSModule } from '@features/course/services/course'; | ||||
| import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| @ -49,6 +50,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|     contentText = ''; | ||||
|     displayDescription = true; | ||||
|     warning = ''; | ||||
|     isIOS = false; | ||||
| 
 | ||||
|     constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) { | ||||
|         super('AddonModResourceIndexComponent', courseContentsPage); | ||||
| @ -61,6 +63,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|         super.ngOnInit(); | ||||
| 
 | ||||
|         this.canGetResource = AddonModResource.isGetResourceWSAvailable(); | ||||
|         this.isIOS = CoreApp.isIOS(); | ||||
| 
 | ||||
|         await this.loadContent(); | ||||
|         try { | ||||
| @ -155,9 +158,10 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|     /** | ||||
|      * Opens a file. | ||||
|      * | ||||
|      * @param useIOSPicker Whether to use the picker in iOS. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async open(): Promise<void> { | ||||
|     async open(useIOSPicker?: boolean): Promise<void> { | ||||
|         let downloadable = await CoreCourseModulePrefetchDelegate.isModuleDownloadable(this.module, this.courseId); | ||||
| 
 | ||||
|         if (downloadable) { | ||||
| @ -166,7 +170,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|             downloadable = await AddonModResourceHelper.isMainFileDownloadable(this.module); | ||||
| 
 | ||||
|             if (downloadable) { | ||||
|                 return AddonModResourceHelper.openModuleFile(this.module, this.courseId); | ||||
|                 return AddonModResourceHelper.openModuleFile(this.module, this.courseId, { useIOSPicker }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,6 @@ | ||||
|     "errorwhileloadingthecontent": "Error while loading the content.", | ||||
|     "modifieddate": "Modified {{$a}}", | ||||
|     "modulenameplural": "Files", | ||||
|     "openthefile": "Open the file", | ||||
|     "openthefile": "Open", | ||||
|     "uploadeddate": "Uploaded {{$a}}" | ||||
| } | ||||
| @ -18,6 +18,7 @@ import { CoreCourse, CoreCourseAnyModuleData, CoreCourseModuleContentFile } from | ||||
| import { CoreCourseModule } from '@features/course/services/course-helper'; | ||||
| import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; | ||||
| import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; | ||||
| import { CoreFileHelper } from '@services/file-helper'; | ||||
| import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| @ -71,6 +72,7 @@ export class AddonModResourceModuleHandlerService implements CoreCourseModuleHan | ||||
|             handlerData.buttons![0].hidden = status !== CoreConstants.DOWNLOADED || | ||||
|                 AddonModResourceHelper.isDisplayedInIframe(module); | ||||
|         }; | ||||
|         const openWithPicker = CoreFileHelper.defaultIsOpenWithPicker(); | ||||
| 
 | ||||
|         const handlerData: CoreCourseModuleHandlerData = { | ||||
|             icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined), | ||||
| @ -88,8 +90,8 @@ export class AddonModResourceModuleHandlerService implements CoreCourseModuleHan | ||||
|             updateStatus: updateStatus.bind(this), | ||||
|             buttons: [{ | ||||
|                 hidden: true, | ||||
|                 icon: 'document', | ||||
|                 label: 'addon.mod_resource.openthefile', | ||||
|                 icon: openWithPicker ? 'fas-share-square' : 'fas-file', | ||||
|                 label: module.name + ': ' + Translate.instant(openWithPicker ? 'core.openwith' : 'addon.mod_resource.openthefile'), | ||||
|                 action: async (event: Event, module: CoreCourseModule, courseId: number): Promise<void> => { | ||||
|                     const hide = await this.hideOpenButton(module, courseId); | ||||
|                     if (!hide) { | ||||
|  | ||||
| @ -25,6 +25,7 @@ import { CoreSites } from '@services/sites'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUtilsOpenFileOptions } from '@services/utils/utils'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModResource, AddonModResourceProvider } from './resource'; | ||||
| 
 | ||||
| @ -171,9 +172,10 @@ export class AddonModResourceHelperProvider { | ||||
|      * | ||||
|      * @param module Module where to get the contents. | ||||
|      * @param courseId Course Id, used for completion purposes. | ||||
|      * @param options Options to open the file. | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     async openModuleFile(module: CoreCourseWSModule, courseId: number): Promise<void> { | ||||
|     async openModuleFile(module: CoreCourseWSModule, courseId: number, options: CoreUtilsOpenFileOptions = {}): Promise<void> { | ||||
|         const modal = await CoreDomUtils.showModalLoading(); | ||||
| 
 | ||||
|         try { | ||||
| @ -184,6 +186,8 @@ export class AddonModResourceHelperProvider { | ||||
|                 AddonModResourceProvider.COMPONENT, | ||||
|                 module.id, | ||||
|                 module.contents, | ||||
|                 undefined, | ||||
|                 options, | ||||
|             ); | ||||
| 
 | ||||
|             try { | ||||
|  | ||||
| @ -7,11 +7,16 @@ | ||||
|         <p *ngIf="fileSizeReadable">{{ fileSizeReadable }}</p> | ||||
|         <p *ngIf="showTime">{{ timemodified * 1000 | coreFormatDate }}</p> | ||||
|     </ion-label> | ||||
|     <div slot="end"> | ||||
|     <div slot="end" class="flex-row"> | ||||
|         <core-download-refresh [status]="state" [enabled]="canDownload" [loading]="isDownloading" | ||||
|             [canTrustDownload]="!alwaysDownload" (action)="download()"> | ||||
|         </core-download-refresh> | ||||
| 
 | ||||
|         <ion-button fill="clear" *ngIf="isDownloaded && isIOS" (click)="openFile(true)" color="dark" | ||||
|             [title]="openButtonLabel | translate"> | ||||
|             <ion-icon slot="icon-only" [name]="openButtonIcon" aria-hidden="true"></ion-icon> | ||||
|         </ion-button> | ||||
| 
 | ||||
|         <ion-button fill="clear" *ngIf="!isDownloading && canDelete" (click)="delete($event)" | ||||
|             [attr.aria-label]="'core.delete' | translate" color="danger"> | ||||
|             <ion-icon slot="icon-only" name="fas-trash" aria-hidden="true"></ion-icon> | ||||
|  | ||||
| @ -21,7 +21,7 @@ import { CoreSites } from '@services/sites'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreUtils, CoreUtilsOpenFileOptions } from '@services/utils/utils'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||
| @ -48,16 +48,21 @@ export class CoreFileComponent implements OnInit, OnDestroy { | ||||
|     @Output() onDelete: EventEmitter<void>; // Will notify when the delete button is clicked.
 | ||||
| 
 | ||||
|     isDownloading?: boolean; | ||||
|     isDownloaded?: boolean; | ||||
|     fileIcon?: string; | ||||
|     fileName!: string; | ||||
|     fileSizeReadable?: string; | ||||
|     state?: string; | ||||
|     timemodified!: number; | ||||
|     isIOS = false; | ||||
|     openButtonIcon = ''; | ||||
|     openButtonLabel = ''; | ||||
| 
 | ||||
|     protected fileUrl!: string; | ||||
|     protected siteId?: string; | ||||
|     protected fileSize?: number; | ||||
|     protected observer?: CoreEventObserver; | ||||
|     protected defaultIsOpenWithPicker = false; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.onDelete = new EventEmitter<void>(); | ||||
| @ -81,6 +86,11 @@ export class CoreFileComponent implements OnInit, OnDestroy { | ||||
|         this.fileSize = this.file.filesize; | ||||
|         this.fileName = this.file.filename || ''; | ||||
| 
 | ||||
|         this.isIOS = CoreApp.isIOS(); | ||||
|         this.defaultIsOpenWithPicker = CoreFileHelper.defaultIsOpenWithPicker(); | ||||
|         this.openButtonIcon = this.defaultIsOpenWithPicker ? 'fas-file' : 'fas-share-square'; | ||||
|         this.openButtonLabel = this.defaultIsOpenWithPicker ? 'core.openfile' : 'core.openwith'; | ||||
| 
 | ||||
|         if (CoreUtils.isTrueOrOne(this.showSize) && this.fileSize && this.fileSize >= 0) { | ||||
|             this.fileSizeReadable = CoreTextUtils.bytesToSize(this.fileSize, 2); | ||||
|         } | ||||
| @ -128,20 +138,28 @@ export class CoreFileComponent implements OnInit, OnDestroy { | ||||
| 
 | ||||
|         this.state = state; | ||||
|         this.isDownloading = this.canDownload && state === CoreConstants.DOWNLOADING; | ||||
|         this.isDownloaded = this.canDownload && CoreFileHelper.isStateDownloaded(state); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convenience function to open a file, downloading it if needed. | ||||
|      * | ||||
|      * @param isOpenButton Whether the open button was clicked. | ||||
|      * @return Promise resolved when file is opened. | ||||
|      */ | ||||
|     protected openFile(): Promise<void> { | ||||
|     openFile(isOpenButton = false): Promise<void> { | ||||
|         const options: CoreUtilsOpenFileOptions = {}; | ||||
|         if (isOpenButton) { | ||||
|             // Use the non-default method.
 | ||||
|             options.useIOSPicker = !this.defaultIsOpenWithPicker; | ||||
|         } | ||||
| 
 | ||||
|         return CoreFileHelper.downloadAndOpenFile(this.file!, this.component, this.componentId, this.state, (event) => { | ||||
|             if (event && 'calculating' in event && event.calculating) { | ||||
|                 // The process is calculating some data required for the download, show the spinner.
 | ||||
|                 this.isDownloading = true; | ||||
|             } | ||||
|         }).catch((error) => { | ||||
|         }, undefined, options).catch((error) => { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true); | ||||
|         }); | ||||
|     } | ||||
| @ -152,7 +170,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { | ||||
|      * @param e Click event. | ||||
|      * @param openAfterDownload Whether the file should be opened after download. | ||||
|      */ | ||||
|     async download(e?: Event, openAfterDownload: boolean = false): Promise<void> { | ||||
|     async download(e?: Event, openAfterDownload = false): Promise<void> { | ||||
|         e && e.preventDefault(); | ||||
|         e && e.stopPropagation(); | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| <form (ngSubmit)="changeName(newFileName, $event)" #nameForm> | ||||
|     <ion-item class="ion-text-wrap item-file" (click)="fileClicked($event)" button> <!-- [class.item-input]="editMode" --> | ||||
|     <ion-item class="ion-text-wrap item-file" (click)="openFile($event)" button detail="false"> | ||||
|         <ion-thumbnail slot="start"> | ||||
|             <img [src]="fileIcon" [alt]="fileExtension" role="presentation" /> | ||||
|         </ion-thumbnail> | ||||
| @ -18,18 +18,25 @@ | ||||
|         </ion-input> | ||||
| 
 | ||||
|         <div class="buttons" slot="end" *ngIf="manage"> | ||||
|             <ion-button *ngIf="!editMode" fill="clear" [core-suppress-events] (onClick)="activateEdit($event)" | ||||
|                 [attr.aria-label]="'core.edit' | translate" color="dark"> | ||||
|                 <ion-icon name="fas-pen" slot="icon-only" aria-hidden="true"></ion-icon> | ||||
|             <ion-button fill="clear" *ngIf="isIOS" (click)="openFile($event, true)" | ||||
|                 [attr.aria-label]="openButtonLabel | translate"> | ||||
|                 <ion-icon slot="icon-only" [name]="openButtonIcon" aria-hidden="true"></ion-icon> | ||||
|             </ion-button> | ||||
| 
 | ||||
|             <ion-button *ngIf="editMode" fill="clear" [attr.aria-label]="'core.save' | translate" color="success" type="submit"> | ||||
|                 <ion-icon name="fas-check" slot="icon-only" aria-hidden="true"></ion-icon> | ||||
|             </ion-button> | ||||
|             <ng-container *ngIf="manage"> | ||||
|                 <ion-button *ngIf="!editMode" fill="clear" [core-suppress-events] (onClick)="activateEdit($event)" | ||||
|                     [attr.aria-label]="'core.edit' | translate" color="dark"> | ||||
|                     <ion-icon name="fas-pen" slot="icon-only" aria-hidden="true"></ion-icon> | ||||
|                 </ion-button> | ||||
| 
 | ||||
|             <ion-button fill="clear" (click)="deleteFile($event)" [attr.aria-label]="'core.delete' | translate" color="danger"> | ||||
|                 <ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon> | ||||
|             </ion-button> | ||||
|                 <ion-button *ngIf="editMode" fill="clear" [attr.aria-label]="'core.save' | translate" color="success" type="submit"> | ||||
|                     <ion-icon name="fas-check" slot="icon-only" aria-hidden="true"></ion-icon> | ||||
|                 </ion-button> | ||||
| 
 | ||||
|                 <ion-button fill="clear" (click)="deleteFile($event)" [attr.aria-label]="'core.delete' | translate" color="danger"> | ||||
|                     <ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon> | ||||
|                 </ion-button> | ||||
|             </ng-container> | ||||
|         </div> | ||||
|     </ion-item> | ||||
| </form> | ||||
|  | ||||
| @ -23,8 +23,9 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreTimeUtils } from '@services/utils/time'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreUtils, CoreUtilsOpenFileOptions } from '@services/utils/utils'; | ||||
| import { CoreForms } from '@singletons/form'; | ||||
| import { CoreApp } from '@services/app'; | ||||
| 
 | ||||
| /** | ||||
|  * Component to handle a local file. Only files inside the app folder can be managed. | ||||
| @ -55,6 +56,11 @@ export class CoreLocalFileComponent implements OnInit { | ||||
|     newFileName = ''; | ||||
|     editMode = false; | ||||
|     relativePath?: string; | ||||
|     isIOS = false; | ||||
|     openButtonIcon = ''; | ||||
|     openButtonLabel = ''; | ||||
| 
 | ||||
|     protected defaultIsOpenWithPicker = false; | ||||
| 
 | ||||
|     /** | ||||
|      * Component being initialized. | ||||
| @ -76,6 +82,10 @@ export class CoreLocalFileComponent implements OnInit { | ||||
| 
 | ||||
|         this.timemodified = CoreTimeUtils.userDate(metadata.modificationTime.getTime(), 'core.strftimedatetimeshort'); | ||||
| 
 | ||||
|         this.isIOS = CoreApp.isIOS(); | ||||
|         this.defaultIsOpenWithPicker = CoreFileHelper.defaultIsOpenWithPicker(); | ||||
|         this.openButtonIcon = this.defaultIsOpenWithPicker ? 'fas-file' : 'fas-share-square'; | ||||
|         this.openButtonLabel = this.defaultIsOpenWithPicker ? 'core.openfile' : 'core.openwith'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -95,11 +105,12 @@ export class CoreLocalFileComponent implements OnInit { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * File clicked. | ||||
|      * Open file. | ||||
|      * | ||||
|      * @param e Click event. | ||||
|      * @param isOpenButton Whether the open button was clicked. | ||||
|      */ | ||||
|     async fileClicked(e: Event): Promise<void> { | ||||
|     async openFile(e: Event, isOpenButton = false): Promise<void> { | ||||
|         if (this.editMode) { | ||||
|             return; | ||||
|         } | ||||
| @ -107,7 +118,7 @@ export class CoreLocalFileComponent implements OnInit { | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
|         if (CoreUtils.isTrueOrOne(this.overrideClick) && this.onClick.observers.length) { | ||||
|         if (!isOpenButton && CoreUtils.isTrueOrOne(this.overrideClick) && this.onClick.observers.length) { | ||||
|             this.onClick.emit(); | ||||
| 
 | ||||
|             return; | ||||
| @ -121,7 +132,13 @@ export class CoreLocalFileComponent implements OnInit { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         CoreUtils.openFile(this.file!.toURL()); | ||||
|         const options: CoreUtilsOpenFileOptions = {}; | ||||
|         if (isOpenButton) { | ||||
|             // Use the non-default method.
 | ||||
|             options.useIOSPicker = !this.defaultIsOpenWithPicker; | ||||
|         } | ||||
| 
 | ||||
|         CoreUtils.openFile(this.file!.toURL(), options); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -173,6 +173,7 @@ export interface EnvironmentConfig { | ||||
|     displayqroncredentialscreen?: boolean; | ||||
|     displayqronsitescreen?: boolean; | ||||
|     forceOpenLinksIn: 'app' | 'browser'; | ||||
|     iosopenfilepicker?: boolean; | ||||
| }; | ||||
| 
 | ||||
| export interface EnvironmentBuild { | ||||
|  | ||||
| @ -17,6 +17,7 @@ import { Directive, Input, OnInit, ElementRef } from '@angular/core'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreCourse, CoreCourseModuleContentFile, CoreCourseWSModule } from '@features/course/services/course'; | ||||
| import { CoreCourseHelper } from '@features/course/services/course-helper'; | ||||
| import { CoreUtilsOpenFileOptions } from '@services/utils/utils'; | ||||
| 
 | ||||
| /** | ||||
|  * Directive to allow downloading and open the main file of a module. | ||||
| @ -36,6 +37,7 @@ export class CoreCourseDownloadModuleMainFileDirective implements OnInit { | ||||
|     @Input() component?: string; // Component to link the file to.
 | ||||
|     @Input() componentId?: string | number; // Component ID to use in conjunction with the component. If not defined, use moduleId.
 | ||||
|     @Input() files?: CoreCourseModuleContentFile[]; // List of files of the module. If not provided, use module.contents.
 | ||||
|     @Input() options?: CoreUtilsOpenFileOptions = {}; | ||||
| 
 | ||||
|     protected element: HTMLElement; | ||||
| 
 | ||||
| @ -73,6 +75,8 @@ export class CoreCourseDownloadModuleMainFileDirective implements OnInit { | ||||
|                     this.component, | ||||
|                     componentId, | ||||
|                     this.files, | ||||
|                     undefined, | ||||
|                     this.options, | ||||
|                 ); | ||||
|             } catch (error) { | ||||
|                 CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true); | ||||
|  | ||||
| @ -31,7 +31,7 @@ import { CoreLogger } from '@singletons/logger'; | ||||
| import { makeSingleton, Translate } from '@singletons'; | ||||
| import { CoreFilepool } from '@services/filepool'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreUtils, CoreUtilsOpenFileOptions } from '@services/utils/utils'; | ||||
| import { | ||||
|     CoreCourseAnyCourseData, | ||||
|     CoreCourseBasicData, | ||||
| @ -656,6 +656,7 @@ export class CoreCourseHelperProvider { | ||||
|      * @param componentId An ID to use in conjunction with the component. | ||||
|      * @param files List of files of the module. If not provided, use module.contents. | ||||
|      * @param siteId The site ID. If not defined, current site. | ||||
|      * @param options Options to open the file. | ||||
|      * @return Resolved on success. | ||||
|      */ | ||||
|     async downloadModuleAndOpenFile( | ||||
| @ -665,6 +666,7 @@ export class CoreCourseHelperProvider { | ||||
|         componentId?: string | number, | ||||
|         files?: CoreCourseModuleContentFile[], | ||||
|         siteId?: string, | ||||
|         options: CoreUtilsOpenFileOptions = {}, | ||||
|     ): Promise<void> { | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
| @ -696,7 +698,7 @@ export class CoreCourseHelperProvider { | ||||
|         const result = await this.downloadModuleWithMainFileIfNeeded(module, courseId, component || '', componentId, files, siteId); | ||||
| 
 | ||||
|         if (CoreUrlUtils.isLocalFileUrl(result.path)) { | ||||
|             return CoreUtils.openFile(result.path); | ||||
|             return CoreUtils.openFile(result.path, options); | ||||
|         } | ||||
| 
 | ||||
|         /* In iOS, if we use the same URL in embedded browser and background download then the download only | ||||
| @ -724,7 +726,7 @@ export class CoreCourseHelperProvider { | ||||
|                 path = await CoreFilepool.getInternalUrlByUrl(siteId, mainFile.fileurl); | ||||
|             } | ||||
| 
 | ||||
|             await CoreUtils.openFile(path); | ||||
|             await CoreUtils.openFile(path, options); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -217,6 +217,7 @@ | ||||
|     "openmodinbrowser": "Open {{$a}} in browser", | ||||
|     "opensecurityquestion": "Open security question", | ||||
|     "opensettings": "Open settings", | ||||
|     "openwith": "Open with...", | ||||
|     "othergroups": "Other groups", | ||||
|     "pagea": "Page {{$a}}", | ||||
|     "parentlanguage": "", | ||||
|  | ||||
| @ -22,7 +22,7 @@ import { CoreSites } from '@services/sites'; | ||||
| import { CoreWS, CoreWSFile } from '@services/ws'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreUtils, CoreUtilsOpenFileOptions } from '@services/utils/utils'; | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { CoreError } from '@classes/errors/error'; | ||||
| import { makeSingleton, Translate } from '@singletons'; | ||||
| @ -34,6 +34,15 @@ import { CoreNetworkError } from '@classes/errors/network-error'; | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreFileHelperProvider { | ||||
| 
 | ||||
|     /** | ||||
|      * Check if the default behaviour of the app is open file with picker. | ||||
|      * | ||||
|      * @return Boolean. | ||||
|      */ | ||||
|     defaultIsOpenWithPicker(): boolean { | ||||
|         return CoreApp.isIOS() && !!CoreConstants.CONFIG.iosopenfilepicker; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convenience function to open a file, downloading it if needed. | ||||
|      * | ||||
| @ -43,6 +52,7 @@ export class CoreFileHelperProvider { | ||||
|      * @param state The file's state. If not provided, it will be calculated. | ||||
|      * @param onProgress Function to call on progress. | ||||
|      * @param siteId The site ID. If not defined, current site. | ||||
|      * @param options Options to open the file. | ||||
|      * @return Resolved on success. | ||||
|      */ | ||||
|     async downloadAndOpenFile( | ||||
| @ -52,6 +62,7 @@ export class CoreFileHelperProvider { | ||||
|         state?: string, | ||||
|         onProgress?: CoreFileHelperOnProgress, | ||||
|         siteId?: string, | ||||
|         options: CoreUtilsOpenFileOptions = {}, | ||||
|     ): Promise<void> { | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
| @ -102,7 +113,7 @@ export class CoreFileHelperProvider { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return CoreUtils.openFile(url); | ||||
|         return CoreUtils.openFile(url, options); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -32,6 +32,7 @@ import { CoreFileSizeSum } from '@services/plugin-file-delegate'; | ||||
| import { CoreViewerQRScannerComponent } from '@features/viewer/components/qr-scanner/qr-scanner'; | ||||
| import { CoreCanceledError } from '@classes/errors/cancelederror'; | ||||
| import { CoreFileEntry } from '@services/file-helper'; | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| 
 | ||||
| type TreeNode<T> = T & { children: TreeNode<T>[] }; | ||||
| 
 | ||||
| @ -907,9 +908,10 @@ export class CoreUtilsProvider { | ||||
|      * Open a file using platform specific method. | ||||
|      * | ||||
|      * @param path The local path of the file to be open. | ||||
|      * @param options Options. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async openFile(path: string): Promise<void> { | ||||
|     async openFile(path: string, options: CoreUtilsOpenFileOptions = {}): Promise<void> { | ||||
|         // Convert the path to a native path if needed.
 | ||||
|         path = CoreFile.unconvertFileSrc(path); | ||||
| 
 | ||||
| @ -931,7 +933,12 @@ export class CoreUtilsProvider { | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await FileOpener.open(path, mimetype || ''); | ||||
|             const useIOSPicker = options.useIOSPicker ?? CoreConstants.CONFIG.iosopenfilepicker; | ||||
|             if (useIOSPicker && CoreApp.isIOS()) { | ||||
|                 await FileOpener.showOpenWithDialog(path, mimetype || ''); | ||||
|             } else { | ||||
|                 await FileOpener.open(path, mimetype || ''); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             this.logger.error('Error opening file ' + path + ' with mimetype ' + mimetype); | ||||
|             this.logger.error('Error: ', JSON.stringify(error)); | ||||
| @ -1703,3 +1710,10 @@ export type CoreMenuItem<T = number> = { | ||||
|     label: string; | ||||
|     value: T | number; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Options for opening a file. | ||||
|  */ | ||||
| export type CoreUtilsOpenFileOptions = { | ||||
|     useIOSPicker?: boolean; // Whether to let user choose app to open the file in iOS. Defaults to false (preview).
 | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user