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