forked from EVOgeek/Vmeda.Online
		
	MOBILE-4015 core: Support refresh on resume with data-app-url
This commit is contained in:
		
							parent
							
								
									910d557b87
								
							
						
					
					
						commit
						dc6ca1f085
					
				| @ -24,6 +24,7 @@ import { | |||||||
|     ViewContainerRef, |     ViewContainerRef, | ||||||
|     ViewChild, |     ViewChild, | ||||||
|     OnDestroy, |     OnDestroy, | ||||||
|  |     Inject, | ||||||
| } from '@angular/core'; | } from '@angular/core'; | ||||||
| import { IonContent } from '@ionic/angular'; | import { IonContent } from '@ionic/angular'; | ||||||
| 
 | 
 | ||||||
| @ -33,7 +34,7 @@ import { CoreIframeUtils, CoreIframeUtilsProvider } from '@services/utils/iframe | |||||||
| import { CoreTextUtils } from '@services/utils/text'; | import { CoreTextUtils } from '@services/utils/text'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
| import { Translate } from '@singletons'; | import { NgZone, Platform, Translate } from '@singletons'; | ||||||
| import { CoreExternalContentDirective } from './external-content'; | import { CoreExternalContentDirective } from './external-content'; | ||||||
| import { CoreLinkDirective } from './link'; | import { CoreLinkDirective } from './link'; | ||||||
| import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; | import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; | ||||||
| @ -46,6 +47,8 @@ import { CoreCancellablePromise } from '@classes/cancellable-promise'; | |||||||
| import { AsyncComponent } from '@classes/async-component'; | import { AsyncComponent } from '@classes/async-component'; | ||||||
| import { CoreText } from '@singletons/text'; | import { CoreText } from '@singletons/text'; | ||||||
| import { CoreDom } from '@singletons/dom'; | import { CoreDom } from '@singletons/dom'; | ||||||
|  | import { CoreEvents } from '@singletons/events'; | ||||||
|  | import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective |  * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective | ||||||
| @ -99,6 +102,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo | |||||||
|         element: ElementRef, |         element: ElementRef, | ||||||
|         @Optional() protected content: IonContent, |         @Optional() protected content: IonContent, | ||||||
|         protected viewContainerRef: ViewContainerRef, |         protected viewContainerRef: ViewContainerRef, | ||||||
|  |         @Optional() @Inject(CORE_REFRESH_CONTEXT) protected refreshContext?: CoreRefreshContext, | ||||||
|     ) { |     ) { | ||||||
|         CoreComponentsRegistry.register(element.nativeElement, this); |         CoreComponentsRegistry.register(element.nativeElement, this); | ||||||
| 
 | 
 | ||||||
| @ -457,8 +461,8 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo | |||||||
|         // Walk through the content to find the links and add our directive to it.
 |         // Walk through the content to find the links and add our directive to it.
 | ||||||
|         // Important: We need to look for links first because in 'img' we add new links without core-link.
 |         // Important: We need to look for links first because in 'img' we add new links without core-link.
 | ||||||
|         anchors.forEach((anchor) => { |         anchors.forEach((anchor) => { | ||||||
|             if (anchor.getAttribute('data-app-url')) { |             if (anchor.dataset.appUrl) { | ||||||
|                 // Link already treated in data-app-url, ignore it.
 |                 // Link already treated in treatAppUrlElements, ignore it.
 | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -563,7 +567,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo | |||||||
|         const appUrlElements = Array.from(div.querySelectorAll<HTMLElement>('*[data-app-url]')); |         const appUrlElements = Array.from(div.querySelectorAll<HTMLElement>('*[data-app-url]')); | ||||||
| 
 | 
 | ||||||
|         appUrlElements.forEach((element) => { |         appUrlElements.forEach((element) => { | ||||||
|             const url = element.getAttribute('data-app-url'); |             const url = element.dataset.appUrl; | ||||||
|             if (!url) { |             if (!url) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| @ -582,8 +586,9 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo | |||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 const confirmMessage = element.getAttribute('data-app-url-confirm'); |                 const confirmMessage = element.dataset.appUrlConfirm; | ||||||
|                 const openInApp = element.getAttribute('data-open-in') === 'app'; |                 const openInApp = element.dataset.openIn === 'app'; | ||||||
|  |                 const refreshOnResume = element.dataset.appUrlResumeAction === 'refresh'; | ||||||
| 
 | 
 | ||||||
|                 if (confirmMessage) { |                 if (confirmMessage) { | ||||||
|                     try { |                     try { | ||||||
| @ -595,10 +600,26 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo | |||||||
| 
 | 
 | ||||||
|                 if (openInApp) { |                 if (openInApp) { | ||||||
|                     site.openInAppWithAutoLoginIfSameSite(url); |                     site.openInAppWithAutoLoginIfSameSite(url); | ||||||
|  | 
 | ||||||
|  |                     if (refreshOnResume && this.refreshContext) { | ||||||
|  |                         // Refresh the context when the IAB is closed.
 | ||||||
|  |                         CoreEvents.once(CoreEvents.IAB_EXIT, () => { | ||||||
|  |                             this.refreshContext?.refreshContext(); | ||||||
|  |                         }); | ||||||
|  |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     site.openInBrowserWithAutoLoginIfSameSite(url, undefined, { |                     site.openInBrowserWithAutoLoginIfSameSite(url, undefined, { | ||||||
|                         showBrowserWarning: !confirmMessage, |                         showBrowserWarning: !confirmMessage, | ||||||
|                     }); |                     }); | ||||||
|  | 
 | ||||||
|  |                     if (refreshOnResume && this.refreshContext) { | ||||||
|  |                         // Refresh the context when the app is resumed.
 | ||||||
|  |                         CoreSubscriptions.once(Platform.resume, () => { | ||||||
|  |                             NgZone.run(async () => { | ||||||
|  |                                 this.refreshContext?.refreshContext(); | ||||||
|  |                             }); | ||||||
|  |                         }); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; | import { Component, ViewChild, OnInit, OnDestroy, forwardRef } from '@angular/core'; | ||||||
| import { IonContent, IonRefresher } from '@ionic/angular'; | import { IonContent, IonRefresher } from '@ionic/angular'; | ||||||
| 
 | 
 | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| @ -36,6 +36,7 @@ import { | |||||||
|     CoreEventObserver, |     CoreEventObserver, | ||||||
| } from '@singletons/events'; | } from '@singletons/events'; | ||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
|  | import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays the contents of a course. |  * Page that displays the contents of a course. | ||||||
| @ -43,8 +44,12 @@ import { CoreNavigator } from '@services/navigator'; | |||||||
| @Component({ | @Component({ | ||||||
|     selector: 'page-core-course-contents', |     selector: 'page-core-course-contents', | ||||||
|     templateUrl: 'contents.html', |     templateUrl: 'contents.html', | ||||||
|  |     providers: [{ | ||||||
|  |         provide: CORE_REFRESH_CONTEXT, | ||||||
|  |         useExisting: forwardRef(() => CoreCourseContentsPage), | ||||||
|  |     }], | ||||||
| }) | }) | ||||||
| export class CoreCourseContentsPage implements OnInit, OnDestroy { | export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshContext { | ||||||
| 
 | 
 | ||||||
|     @ViewChild(IonContent) content?: IonContent; |     @ViewChild(IonContent) content?: IonContent; | ||||||
|     @ViewChild(CoreCourseFormatComponent) formatComponent?: CoreCourseFormatComponent; |     @ViewChild(CoreCourseFormatComponent) formatComponent?: CoreCourseFormatComponent; | ||||||
| @ -127,7 +132,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | |||||||
|             CoreEvents.COMPLETION_MODULE_VIEWED, |             CoreEvents.COMPLETION_MODULE_VIEWED, | ||||||
|             (data) => { |             (data) => { | ||||||
|                 if (data && data.courseId == this.course.id) { |                 if (data && data.courseId == this.course.id) { | ||||||
|                     this.refreshAfterCompletionChange(true); |                     this.showLoadingAndRefresh(true, false); | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|         ); |         ); | ||||||
| @ -141,7 +146,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             this.refreshAfterCompletionChange(false); |             this.showLoadingAndRefresh(false, false); | ||||||
| 
 | 
 | ||||||
|             if (data.warnings && data.warnings[0]) { |             if (data.warnings && data.warnings[0]) { | ||||||
|                 CoreDomUtils.showErrorModal(data.warnings[0]); |                 CoreDomUtils.showErrorModal(data.warnings[0]); | ||||||
| @ -315,7 +320,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|         await CoreUtils.ignoreErrors(this.invalidateData()); |         await CoreUtils.ignoreErrors(this.invalidateData()); | ||||||
| 
 | 
 | ||||||
|         await this.refreshAfterCompletionChange(true); |         await this.showLoadingAndRefresh(true, false); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -341,9 +346,10 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | |||||||
|      * Refresh list after a completion change since there could be new activities. |      * Refresh list after a completion change since there could be new activities. | ||||||
|      * |      * | ||||||
|      * @param sync If it should try to sync. |      * @param sync If it should try to sync. | ||||||
|  |      * @param invalidateData Whether to invalidate data. Set it to false if data has already been invalidated. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected async refreshAfterCompletionChange(sync?: boolean): Promise<void> { |     protected async showLoadingAndRefresh(sync = false, invalidateData = true): Promise<void> { | ||||||
|         // Save scroll position to restore it once done.
 |         // Save scroll position to restore it once done.
 | ||||||
|         const scrollElement = await this.content?.getScrollElement(); |         const scrollElement = await this.content?.getScrollElement(); | ||||||
|         const scrollTop = scrollElement?.scrollTop || 0; |         const scrollTop = scrollElement?.scrollTop || 0; | ||||||
| @ -353,6 +359,10 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | |||||||
|         this.content?.scrollToTop(0); // Scroll top so the spinner is seen.
 |         this.content?.scrollToTop(0); // Scroll top so the spinner is seen.
 | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|  |             if (invalidateData) { | ||||||
|  |                 await CoreUtils.ignoreErrors(this.invalidateData()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             await this.loadData(true, sync); |             await this.loadData(true, sync); | ||||||
| 
 | 
 | ||||||
|             await this.formatComponent?.doRefresh(undefined, undefined, true); |             await this.formatComponent?.doRefresh(undefined, undefined, true); | ||||||
| @ -366,6 +376,13 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @inheritdoc | ||||||
|  |      */ | ||||||
|  |     async refreshContext(): Promise<void> { | ||||||
|  |         await this.showLoadingAndRefresh(true, true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -173,6 +173,31 @@ export class CoreEvents { | |||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Listen once for a certain event. To stop listening to the event (in case it wasn't triggered): | ||||||
|  |      * let observer = eventsProvider.on('something', myCallBack); | ||||||
|  |      * ... | ||||||
|  |      * observer.off(); | ||||||
|  |      * | ||||||
|  |      * @param eventName Name of the event to listen to. | ||||||
|  |      * @param callBack Function to call when the event is triggered. | ||||||
|  |      * @param siteId Site where to trigger the event. Undefined won't check the site. | ||||||
|  |      * @return Observer to stop listening. | ||||||
|  |      */ | ||||||
|  |     static once<Fallback = unknown, Event extends string = string>( | ||||||
|  |         eventName: Event, | ||||||
|  |         callBack: (value: CoreEventData<Event, Fallback> & CoreEventSiteData) => void, | ||||||
|  |         siteId?: string, | ||||||
|  |     ): CoreEventObserver { | ||||||
|  |         const listener = CoreEvents.on<Fallback, Event>(eventName, (value) => { | ||||||
|  |             setTimeout(() => listener.off(), 0); | ||||||
|  | 
 | ||||||
|  |             callBack(value); | ||||||
|  |         }, siteId); | ||||||
|  | 
 | ||||||
|  |         return listener; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Listen for several events. To stop listening to the events: |      * Listen for several events. To stop listening to the events: | ||||||
|      * let observer = eventsProvider.onMultiple(['something', 'another'], myCallBack); |      * let observer = eventsProvider.onMultiple(['something', 'another'], myCallBack); | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								src/core/utils/refresh-context.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/core/utils/refresh-context.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||||
|  | //
 | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||||
|  | // you may not use this file except in compliance with the License.
 | ||||||
|  | // You may obtain a copy of the License at
 | ||||||
|  | //
 | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | //
 | ||||||
|  | // Unless required by applicable law or agreed to in writing, software
 | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||||
|  | // See the License for the specific language governing permissions and
 | ||||||
|  | // limitations under the License.
 | ||||||
|  | 
 | ||||||
|  | import { InjectionToken } from '@angular/core'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Context to refresh data when a certain action happens. | ||||||
|  |  */ | ||||||
|  | export interface CoreRefreshContext { | ||||||
|  |     /** | ||||||
|  |      * Refresh the context. | ||||||
|  |      */ | ||||||
|  |     refreshContext(): Promise<void>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const CORE_REFRESH_CONTEXT = new InjectionToken<CoreRefreshContext>('CORE_REFRESH_CONTEXT'); | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user