commit
						155ec962ed
					
				| @ -1469,6 +1469,7 @@ | |||||||
|   "core.cannotconnecttrouble": "local_moodlemobileapp", |   "core.cannotconnecttrouble": "local_moodlemobileapp", | ||||||
|   "core.cannotconnectverify": "local_moodlemobileapp", |   "core.cannotconnectverify": "local_moodlemobileapp", | ||||||
|   "core.cannotdownloadfiles": "local_moodlemobileapp", |   "core.cannotdownloadfiles": "local_moodlemobileapp", | ||||||
|  |   "core.cannotlogoutpageblocks": "local_moodlemobileapp", | ||||||
|   "core.cannotopeninapp": "local_moodlemobileapp", |   "core.cannotopeninapp": "local_moodlemobileapp", | ||||||
|   "core.cannotopeninappdownload": "local_moodlemobileapp", |   "core.cannotopeninappdownload": "local_moodlemobileapp", | ||||||
|   "core.captureaudio": "local_moodlemobileapp", |   "core.captureaudio": "local_moodlemobileapp", | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ import { CoreLogger } from '@singletons/logger'; | |||||||
| import { Translate } from '@singletons'; | import { Translate } from '@singletons'; | ||||||
| import { CoreIonLoadingElement } from './ion-loading'; | import { CoreIonLoadingElement } from './ion-loading'; | ||||||
| import { CoreLang } from '@services/lang'; | import { CoreLang } from '@services/lang'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; | ||||||
| import { asyncInstance, AsyncInstance } from '../utils/async-instance'; | import { asyncInstance, AsyncInstance } from '../utils/async-instance'; | ||||||
| import { CoreDatabaseTable } from './database/database-table'; | import { CoreDatabaseTable } from './database/database-table'; | ||||||
| import { CoreDatabaseCachingStrategy } from './database/database-table-proxy'; | import { CoreDatabaseCachingStrategy } from './database/database-table-proxy'; | ||||||
| @ -1389,9 +1389,88 @@ export class CoreSite { | |||||||
|     /** |     /** | ||||||
|      * Get the public config of this site. |      * Get the public config of this site. | ||||||
|      * |      * | ||||||
|  |      * @param options Options. | ||||||
|      * @return Promise resolved with public config. Rejected with an object if error, see CoreWSProvider.callAjax. |      * @return Promise resolved with public config. Rejected with an object if error, see CoreWSProvider.callAjax. | ||||||
|      */ |      */ | ||||||
|     async getPublicConfig(): Promise<CoreSitePublicConfigResponse> { |     async getPublicConfig(options: { readingStrategy?: CoreSitesReadingStrategy } = {}): Promise<CoreSitePublicConfigResponse> { | ||||||
|  |         if (!this.db) { | ||||||
|  |             return this.requestPublicConfig(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const method = 'tool_mobile_get_public_config'; | ||||||
|  |         const cacheId = this.getCacheId(method, {}); | ||||||
|  |         const cachePreSets: CoreSiteWSPreSets = { | ||||||
|  |             getFromCache: true, | ||||||
|  |             saveToCache: true, | ||||||
|  |             emergencyCache: true, | ||||||
|  |             ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if (this.offlineDisabled) { | ||||||
|  |             // Offline is disabled, don't use cache.
 | ||||||
|  |             cachePreSets.getFromCache = false; | ||||||
|  |             cachePreSets.saveToCache = false; | ||||||
|  |             cachePreSets.emergencyCache = false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Check for an ongoing identical request if we're not ignoring cache.
 | ||||||
|  |         if (cachePreSets.getFromCache && this.ongoingRequests[cacheId]) { | ||||||
|  |             const response = await this.ongoingRequests[cacheId]; | ||||||
|  | 
 | ||||||
|  |             return response; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const promise = this.getFromCache<CoreSitePublicConfigResponse>(method, {}, cachePreSets, false).catch(async () => { | ||||||
|  |             if (cachePreSets.forceOffline) { | ||||||
|  |                 // Don't call the WS, just fail.
 | ||||||
|  |                 throw new CoreError( | ||||||
|  |                     Translate.instant('core.cannotconnect', { $a: CoreSite.MINIMUM_MOODLE_VERSION }), | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Call the WS.
 | ||||||
|  |             try { | ||||||
|  |                 const config = await this.requestPublicConfig(); | ||||||
|  | 
 | ||||||
|  |                 if (cachePreSets.saveToCache) { | ||||||
|  |                     this.saveToCache(method, {}, config, cachePreSets); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return config; | ||||||
|  |             } catch (error) { | ||||||
|  |                 cachePreSets.omitExpires = true; | ||||||
|  |                 cachePreSets.getFromCache = true; | ||||||
|  | 
 | ||||||
|  |                 try { | ||||||
|  |                     return await this.getFromCache<CoreSitePublicConfigResponse>(method, {}, cachePreSets, true); | ||||||
|  |                 } catch { | ||||||
|  |                     throw error; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.ongoingRequests[cacheId] = promise; | ||||||
|  | 
 | ||||||
|  |         // Clear ongoing request after setting the promise (just in case it's already resolved).
 | ||||||
|  |         try { | ||||||
|  |             const response = await promise; | ||||||
|  | 
 | ||||||
|  |             // We pass back a clone of the original object, this may prevent errors if in the callback the object is modified.
 | ||||||
|  |             return response; | ||||||
|  |         } finally { | ||||||
|  |             // Make sure we don't clear the promise of a newer request that ignores the cache.
 | ||||||
|  |             if (this.ongoingRequests[cacheId] === promise) { | ||||||
|  |                 delete this.ongoingRequests[cacheId]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Perform a request to the server to get the public config of this site. | ||||||
|  |      * | ||||||
|  |      * @return Promise resolved with public config. | ||||||
|  |      */ | ||||||
|  |     protected async requestPublicConfig(): Promise<CoreSitePublicConfigResponse> { | ||||||
|         const preSets: CoreWSAjaxPreSets = { |         const preSets: CoreWSAjaxPreSets = { | ||||||
|             siteUrl: this.siteUrl, |             siteUrl: this.siteUrl, | ||||||
|         }; |         }; | ||||||
|  | |||||||
| @ -11,10 +11,12 @@ | |||||||
|     </ion-toolbar> |     </ion-toolbar> | ||||||
| </ion-header> | </ion-header> | ||||||
| <ion-content> | <ion-content> | ||||||
|     <ion-list id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label"> |     <core-loading [hideUntil]="loaded"> | ||||||
|  |         <ion-list *ngIf="loaded" id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label"> | ||||||
|             <ng-container *ngFor="let section of sectionsToRender"> |             <ng-container *ngFor="let section of sectionsToRender"> | ||||||
|                 <ion-item *ngIf="allSectionId == section.id" class="divider core-course-index-all" |                 <ion-item *ngIf="allSectionId == section.id" class="divider core-course-index-all" | ||||||
|                 (click)="selectSectionOrModule($event, section.id)" button [class.item-current]="selectedId === section.id" detail="false"> |                     (click)="selectSectionOrModule($event, section.id)" button [class.item-current]="selectedId === section.id" | ||||||
|  |                     detail="false"> | ||||||
|                     <ion-label> |                     <ion-label> | ||||||
|                         <h2> |                         <h2> | ||||||
|                             <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id"> |                             <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id"> | ||||||
| @ -51,12 +53,14 @@ | |||||||
|                     <div id="core-course-index-section-{{section.id}}"> |                     <div id="core-course-index-section-{{section.id}}"> | ||||||
|                         <ng-container *ngIf="section.expanded"> |                         <ng-container *ngIf="section.expanded"> | ||||||
|                             <ng-container *ngFor="let module of section.modules"> |                             <ng-container *ngFor="let module of section.modules"> | ||||||
|                             <ion-item class="module" [class.item-dimmed]="!module.visible" [class.item-hightlighted]="section.highlighted" |                                 <ion-item class="module" [class.item-dimmed]="!module.visible" | ||||||
|  |                                     [class.item-hightlighted]="section.highlighted" | ||||||
|                                     (click)="selectSectionOrModule($event, section.id, module.id)" button> |                                     (click)="selectSectionOrModule($event, section.id, module.id)" button> | ||||||
|                                     <ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined" |                                     <ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined" | ||||||
|                                         slot="start" aria-hidden="true"></ion-icon> |                                         slot="start" aria-hidden="true"></ion-icon> | ||||||
|                                     <ion-icon class="completioninfo completion_incomplete" name="far-circle" |                                     <ion-icon class="completioninfo completion_incomplete" name="far-circle" | ||||||
|                                     *ngIf="module.completionStatus === 0" slot="start" [attr.aria-label]="'core.course.todo' | translate"> |                                         *ngIf="module.completionStatus === 0" slot="start" | ||||||
|  |                                         [attr.aria-label]="'core.course.todo' | translate"> | ||||||
|                                     </ion-icon> |                                     </ion-icon> | ||||||
|                                     <ion-icon class="completioninfo completion_complete" name="fas-circle" |                                     <ion-icon class="completioninfo completion_complete" name="fas-circle" | ||||||
|                                         *ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start" |                                         *ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start" | ||||||
| @ -81,4 +85,5 @@ | |||||||
|                 </ng-container> |                 </ng-container> | ||||||
|             </ng-container> |             </ng-container> | ||||||
|         </ion-list> |         </ion-list> | ||||||
|  |     </core-loading> | ||||||
| </ion-content> | </ion-content> | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ import { | |||||||
| import { CoreCourseHelper, CoreCourseSection } from '@features/course/services/course-helper'; | import { CoreCourseHelper, CoreCourseSection } from '@features/course/services/course-helper'; | ||||||
| import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate'; | import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate'; | ||||||
| import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; | import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { ModalController } from '@singletons'; | import { ModalController } from '@singletons'; | ||||||
| import { CoreDom } from '@singletons/dom'; | import { CoreDom } from '@singletons/dom'; | ||||||
| 
 | 
 | ||||||
| @ -41,6 +42,7 @@ export class CoreCourseCourseIndexComponent implements OnInit { | |||||||
|     allSectionId = CoreCourseProvider.ALL_SECTIONS_ID; |     allSectionId = CoreCourseProvider.ALL_SECTIONS_ID; | ||||||
|     highlighted?: string; |     highlighted?: string; | ||||||
|     sectionsToRender: CourseIndexSection[] = []; |     sectionsToRender: CourseIndexSection[] = []; | ||||||
|  |     loaded = false; | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         protected elementRef: ElementRef, |         protected elementRef: ElementRef, | ||||||
| @ -109,6 +111,13 @@ export class CoreCourseCourseIndexComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|         this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course); |         this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course); | ||||||
| 
 | 
 | ||||||
|  |         // Wait a bit to render the data, otherwise the modal takes a while to appear in big courses or slow devices.
 | ||||||
|  |         await CoreUtils.wait(400); | ||||||
|  | 
 | ||||||
|  |         this.loaded = true; | ||||||
|  | 
 | ||||||
|  |         await CoreUtils.nextTick(); | ||||||
|  | 
 | ||||||
|         CoreDom.scrollToElement( |         CoreDom.scrollToElement( | ||||||
|             this.elementRef.nativeElement, |             this.elementRef.nativeElement, | ||||||
|             '.item.item-current', |             '.item.item-current', | ||||||
|  | |||||||
| @ -130,7 +130,27 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { | |||||||
|      * @return Promise resolved when ready. |      * @return Promise resolved when ready. | ||||||
|      */ |      */ | ||||||
|     protected async initCordovaMediaPlugin(): Promise<void> { |     protected async initCordovaMediaPlugin(): Promise<void> { | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             await this.createFileAndMediaInstance(); | ||||||
|  | 
 | ||||||
|  |             this.readyToCapture = true; | ||||||
|  |             this.previewMedia = this.previewAudio?.nativeElement; | ||||||
|  |         } catch (error) { | ||||||
|  |             this.dismissWithError(-1, error.message || error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create a file entry and the cordova media instance. | ||||||
|  |      */ | ||||||
|  |     protected async createFileAndMediaInstance(): Promise<void> { | ||||||
|         this.filePath = this.getFilePath(); |         this.filePath = this.getFilePath(); | ||||||
|  | 
 | ||||||
|  |         // First create the file.
 | ||||||
|  |         this.fileEntry = await CoreFile.createFile(this.filePath); | ||||||
|  | 
 | ||||||
|  |         // Now create the media instance.
 | ||||||
|         let absolutePath = CoreText.concatenatePaths(CoreFile.getBasePathInstant(), this.filePath); |         let absolutePath = CoreText.concatenatePaths(CoreFile.getBasePathInstant(), this.filePath); | ||||||
| 
 | 
 | ||||||
|         if (Platform.is('ios')) { |         if (Platform.is('ios')) { | ||||||
| @ -138,17 +158,21 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { | |||||||
|             absolutePath = absolutePath.replace(/^file:\/\//, ''); |             absolutePath = absolutePath.replace(/^file:\/\//, ''); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |  | ||||||
|             // First create the file.
 |  | ||||||
|             this.fileEntry = await CoreFile.createFile(this.filePath); |  | ||||||
| 
 |  | ||||||
|             // Now create the media instance.
 |  | ||||||
|         this.mediaFile = Media.create(absolutePath); |         this.mediaFile = Media.create(absolutePath); | ||||||
|             this.readyToCapture = true; |  | ||||||
|             this.previewMedia = this.previewAudio?.nativeElement; |  | ||||||
|         } catch (error) { |  | ||||||
|             this.dismissWithError(-1, error.message || error); |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Reset the file and the cordova media instance. | ||||||
|  |      */ | ||||||
|  |     protected async resetCordovaMediaCapture(): Promise<void> { | ||||||
|  |         if (this.filePath) { | ||||||
|  |             // Remove old file, don't block the user for this.
 | ||||||
|  |             CoreFile.removeFile(this.filePath); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.mediaFile?.release(); | ||||||
|  | 
 | ||||||
|  |         await this.createFileAndMediaInstance(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -205,7 +229,8 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (!this.streamVideo) { |             const streamVideo = this.streamVideo; | ||||||
|  |             if (!streamVideo) { | ||||||
|                 throw new CoreError('Video element not found.'); |                 throw new CoreError('Video element not found.'); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -221,7 +246,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { | |||||||
|             }, 10000); |             }, 10000); | ||||||
| 
 | 
 | ||||||
|             // Listen for stream ready to display the stream.
 |             // Listen for stream ready to display the stream.
 | ||||||
|             this.streamVideo.nativeElement.onloadedmetadata = (): void => { |             streamVideo.nativeElement.onloadedmetadata = (): void => { | ||||||
|                 if (hasLoaded) { |                 if (hasLoaded) { | ||||||
|                     // Already loaded or timeout triggered, stop.
 |                     // Already loaded or timeout triggered, stop.
 | ||||||
|                     return; |                     return; | ||||||
| @ -230,19 +255,13 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { | |||||||
|                 hasLoaded = true; |                 hasLoaded = true; | ||||||
|                 clearTimeout(waitTimeout); |                 clearTimeout(waitTimeout); | ||||||
|                 this.readyToCapture = true; |                 this.readyToCapture = true; | ||||||
|                 this.streamVideo!.nativeElement.onloadedmetadata = null; |                 streamVideo.nativeElement.onloadedmetadata = null; | ||||||
|                 // Force change detection. Angular doesn't detect these async operations.
 |                 // Force change detection. Angular doesn't detect these async operations.
 | ||||||
|                 this.changeDetectorRef.detectChanges(); |                 this.changeDetectorRef.detectChanges(); | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             // Set the stream as the source of the video.
 |             // Set the stream as the source of the video.
 | ||||||
|             if ('srcObject' in this.streamVideo.nativeElement) { |             streamVideo.nativeElement.srcObject = this.localMediaStream; | ||||||
|                 this.streamVideo.nativeElement.srcObject = this.localMediaStream; |  | ||||||
|             } else { |  | ||||||
|                 // Fallback for old browsers.
 |  | ||||||
|                 // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject#Examples
 |  | ||||||
|                 this.streamVideo.nativeElement.src = window.URL.createObjectURL(this.localMediaStream); |  | ||||||
|             } |  | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             this.dismissWithError(-1, error.message || error); |             this.dismissWithError(-1, error.message || error); | ||||||
|         } |         } | ||||||
| @ -375,7 +394,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { | |||||||
|             this.imgCanvas.nativeElement.getContext('2d').drawImage(this.streamVideo?.nativeElement, 0, 0, width, height); |             this.imgCanvas.nativeElement.getContext('2d').drawImage(this.streamVideo?.nativeElement, 0, 0, width, height); | ||||||
| 
 | 
 | ||||||
|             // Convert the image to blob and show it in an image element.
 |             // Convert the image to blob and show it in an image element.
 | ||||||
|             this.imgCanvas.nativeElement.toBlob((blob) => { |             this.imgCanvas.nativeElement.toBlob((blob: Blob) => { | ||||||
|                 loadingModal.dismiss(); |                 loadingModal.dismiss(); | ||||||
| 
 | 
 | ||||||
|                 this.mediaBlob = blob; |                 this.mediaBlob = blob; | ||||||
| @ -410,11 +429,15 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { | |||||||
|     /** |     /** | ||||||
|      * Discard the captured media. |      * Discard the captured media. | ||||||
|      */ |      */ | ||||||
|     discard(): void { |     async discard(): Promise<void> { | ||||||
|         this.previewMedia?.pause(); |         this.previewMedia?.pause(); | ||||||
|         this.streamVideo?.nativeElement.play(); |         this.streamVideo?.nativeElement.play(); | ||||||
|         this.audioDrawer?.start(); |         this.audioDrawer?.start(); | ||||||
| 
 | 
 | ||||||
|  |         if (this.isCordovaAudioCapture) { | ||||||
|  |             await this.resetCordovaMediaCapture(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         this.hasCaptured = false; |         this.hasCaptured = false; | ||||||
|         this.isCapturing = false; |         this.isCapturing = false; | ||||||
|         this.resetChrono = true; |         this.resetChrono = true; | ||||||
|  | |||||||
| @ -550,10 +550,8 @@ export class CoreFileUploaderHelperProvider { | |||||||
|             media = medias[0]; // We used limit 1, we only want 1 media.
 |             media = medias[0]; // We used limit 1, we only want 1 media.
 | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
| 
 | 
 | ||||||
|             if (isAudio && this.isNoAppError(error) && CoreApp.isMobile() && |             if (isAudio && this.isNoAppError(error) && CoreApp.isMobile()) { | ||||||
|                     (!Platform.is('android') || CoreApp.getPlatformMajorVersion() < 10)) { |  | ||||||
|                 // No app to record audio, fallback to capture it ourselves.
 |                 // No app to record audio, fallback to capture it ourselves.
 | ||||||
|                 // In Android it will only be done in Android 9 or lower because there's a bug in the plugin.
 |  | ||||||
|                 try { |                 try { | ||||||
|                     media = await CoreFileUploader.captureAudioInApp(); |                     media = await CoreFileUploader.captureAudioInApp(); | ||||||
|                 } catch (error) { |                 } catch (error) { | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/co | |||||||
| import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | ||||||
| 
 | 
 | ||||||
| import { CoreApp } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreLoginHelper } from '@features/login/services/login-helper'; | import { CoreLoginHelper } from '@features/login/services/login-helper'; | ||||||
| @ -132,7 +132,9 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { | |||||||
|      * Get some data (like identity providers) from the site config. |      * Get some data (like identity providers) from the site config. | ||||||
|      */ |      */ | ||||||
|     protected async checkSiteConfig(site: CoreSite): Promise<void> { |     protected async checkSiteConfig(site: CoreSite): Promise<void> { | ||||||
|         this.siteConfig = await CoreUtils.ignoreErrors(site.getPublicConfig()); |         this.siteConfig = await CoreUtils.ignoreErrors(site.getPublicConfig({ | ||||||
|  |             readingStrategy: CoreSitesReadingStrategy.PREFER_NETWORK, | ||||||
|  |         })); | ||||||
| 
 | 
 | ||||||
|         if (!this.siteConfig) { |         if (!this.siteConfig) { | ||||||
|             return; |             return; | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
| 
 | 
 | ||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreCronHandler } from '@services/cron'; | import { CoreCronHandler } from '@services/cron'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| 
 | 
 | ||||||
| @ -39,7 +39,9 @@ export class CoreLoginCronHandlerService implements CoreCronHandler { | |||||||
|         // Do not check twice in the same 10 minutes.
 |         // Do not check twice in the same 10 minutes.
 | ||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
| 
 | 
 | ||||||
|         const config = await CoreUtils.ignoreErrors(site.getPublicConfig()); |         const config = await CoreUtils.ignoreErrors(site.getPublicConfig({ | ||||||
|  |             readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK, | ||||||
|  |         })); | ||||||
| 
 | 
 | ||||||
|         CoreUtils.ignoreErrors(CoreSites.checkApplication(config)); |         CoreUtils.ignoreErrors(CoreSites.checkApplication(config)); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -78,7 +78,7 @@ export class CoreMainMenuDeepLinkManager { | |||||||
|             if (!params?.course) { |             if (!params?.course) { | ||||||
|                 CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params); |                 CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params); | ||||||
|             } else { |             } else { | ||||||
|                 CoreCourse.openCourse(params.course, params); |                 CoreCourse.openCourse(params.course, navOptions); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             CoreNavigator.navigateToSitePath(path, { |             CoreNavigator.navigateToSitePath(path, { | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ import { | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { ModalController } from '@singletons'; | import { ModalController, Translate } from '@singletons'; | ||||||
| import { Subscription } from 'rxjs'; | import { Subscription } from 'rxjs'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -172,6 +172,12 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { | |||||||
|      * @param event Click event |      * @param event Click event | ||||||
|      */ |      */ | ||||||
|     async logout(event: Event): Promise<void> { |     async logout(event: Event): Promise<void> { | ||||||
|  |         if (CoreNavigator.currentRouteCanBlockLeave()) { | ||||||
|  |             await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks')); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if (this.removeAccountOnLogout) { |         if (this.removeAccountOnLogout) { | ||||||
|             // Ask confirm.
 |             // Ask confirm.
 | ||||||
|             const siteName = this.siteName ? |             const siteName = this.siteName ? | ||||||
| @ -200,6 +206,12 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { | |||||||
|      * @param event Click event |      * @param event Click event | ||||||
|      */ |      */ | ||||||
|     async switchAccounts(event: Event): Promise<void> { |     async switchAccounts(event: Event): Promise<void> { | ||||||
|  |         if (CoreNavigator.currentRouteCanBlockLeave()) { | ||||||
|  |             await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks')); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         const thisModal = await ModalController.getTop(); |         const thisModal = await ModalController.getTop(); | ||||||
| 
 | 
 | ||||||
|         event.preventDefault(); |         event.preventDefault(); | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ | |||||||
|     "cannotconnecttrouble": "We're having trouble connecting to your site.", |     "cannotconnecttrouble": "We're having trouble connecting to your site.", | ||||||
|     "cannotconnectverify": "<strong>Please check the address is correct.</strong>", |     "cannotconnectverify": "<strong>Please check the address is correct.</strong>", | ||||||
|     "cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.", |     "cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.", | ||||||
|  |     "cannotlogoutpageblocks": "Please save or discard your changes before continuing.", | ||||||
|     "cannotopeninapp": "This file may not work as expected on this device. Would you like to open it anyway?", |     "cannotopeninapp": "This file may not work as expected on this device. Would you like to open it anyway?", | ||||||
|     "cannotopeninappdownload": "This file may not work as expected on this device. Would you like to download it anyway?", |     "cannotopeninappdownload": "This file may not work as expected on this device. Would you like to download it anyway?", | ||||||
|     "captureaudio": "Record audio", |     "captureaudio": "Record audio", | ||||||
|  | |||||||
| @ -658,6 +658,15 @@ export class CoreNavigatorService { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if the current route page can block leaving the route. | ||||||
|  |      * | ||||||
|  |      * @return Whether the current route page can block leaving the route. | ||||||
|  |      */ | ||||||
|  |     currentRouteCanBlockLeave(): boolean { | ||||||
|  |         return !!this.getCurrentRoute().snapshot.routeConfig?.canDeactivate?.length; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const CoreNavigator = makeSingleton(CoreNavigatorService); | export const CoreNavigator = makeSingleton(CoreNavigatorService); | ||||||
|  | |||||||
| @ -897,7 +897,9 @@ export class CoreSitesProvider { | |||||||
|      */ |      */ | ||||||
|     protected async getPublicConfigAndCheckApplication(site: CoreSite): Promise<void> { |     protected async getPublicConfigAndCheckApplication(site: CoreSite): Promise<void> { | ||||||
|         try { |         try { | ||||||
|             const config = await site.getPublicConfig(); |             const config = await site.getPublicConfig({ | ||||||
|  |                 readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK, | ||||||
|  |             }); | ||||||
| 
 | 
 | ||||||
|             await this.checkApplication(config); |             await this.checkApplication(config); | ||||||
|         } catch { |         } catch { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user