forked from CIT/Vmeda.Online
		
	MOBILE-3109 addon: Add return types to all addons except mod
This commit is contained in:
		
							parent
							
								
									0c6c1b6383
								
							
						
					
					
						commit
						b2497a1dd0
					
				| @ -1,6 +1,6 @@ | |||||||
| <ion-header> | <ion-header> | ||||||
|     <ion-navbar core-back-button> |     <ion-navbar core-back-button> | ||||||
|         <ion-title>{{badge.name}}</ion-title> |         <ion-title>{{badge && badge.name}}</ion-title> | ||||||
|     </ion-navbar> |     </ion-navbar> | ||||||
| </ion-header> | </ion-header> | ||||||
| <ion-content> | <ion-content> | ||||||
| @ -9,7 +9,7 @@ | |||||||
|     </ion-refresher> |     </ion-refresher> | ||||||
|     <core-loading [hideUntil]="badgeLoaded"> |     <core-loading [hideUntil]="badgeLoaded"> | ||||||
| 
 | 
 | ||||||
|         <ion-item-group> |         <ion-item-group *ngIf="badge"> | ||||||
|             <ion-item text-wrap class="item-avatar-center"> |             <ion-item text-wrap class="item-avatar-center"> | ||||||
|                 <img *ngIf="badge.badgeurl" class="avatar" [src]="badge.badgeurl" core-external-content [alt]="badge.name"> |                 <img *ngIf="badge.badgeurl" class="avatar" [src]="badge.badgeurl" core-external-content [alt]="badge.name"> | ||||||
|                 <ion-badge color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire"> |                 <ion-badge color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire"> | ||||||
| @ -30,7 +30,7 @@ | |||||||
|             </ion-item> |             </ion-item> | ||||||
|         </ion-item-group> |         </ion-item-group> | ||||||
| 
 | 
 | ||||||
|         <ion-item-group> |         <ion-item-group *ngIf="badge"> | ||||||
|             <ion-item-divider> |             <ion-item-divider> | ||||||
|                 <h2>{{ 'addon.badges.issuerdetails' | translate}}</h2> |                 <h2>{{ 'addon.badges.issuerdetails' | translate}}</h2> | ||||||
|             </ion-item-divider> |             </ion-item-divider> | ||||||
| @ -48,7 +48,7 @@ | |||||||
|             </ion-item> |             </ion-item> | ||||||
|         </ion-item-group> |         </ion-item-group> | ||||||
| 
 | 
 | ||||||
|         <ion-item-group> |         <ion-item-group *ngIf="badge"> | ||||||
|             <ion-item-divider> |             <ion-item-divider> | ||||||
|                 <h2>{{ 'addon.badges.badgedetails' | translate}}</h2> |                 <h2>{{ 'addon.badges.badgedetails' | translate}}</h2> | ||||||
|             </ion-item-divider> |             </ion-item-divider> | ||||||
| @ -99,7 +99,7 @@ | |||||||
|             <!-- Criteria (not yet avalaible) --> |             <!-- Criteria (not yet avalaible) --> | ||||||
|         </ion-item-group> |         </ion-item-group> | ||||||
| 
 | 
 | ||||||
|         <ion-item-group> |         <ion-item-group *ngIf="badge"> | ||||||
|             <ion-item-divider> |             <ion-item-divider> | ||||||
|                 <h2>{{ 'addon.badges.issuancedetails' | translate}}</h2> |                 <h2>{{ 'addon.badges.issuancedetails' | translate}}</h2> | ||||||
|             </ion-item-divider> |             </ion-item-divider> | ||||||
| @ -120,7 +120,7 @@ | |||||||
|         </ion-item-group> |         </ion-item-group> | ||||||
| 
 | 
 | ||||||
|         <!-- Endorsement --> |         <!-- Endorsement --> | ||||||
|         <ion-item-group *ngIf="badge.endorsement"> |         <ion-item-group *ngIf="badge && badge.endorsement"> | ||||||
|             <ion-item-divider> |             <ion-item-divider> | ||||||
|                 <h2>{{ 'addon.badges.bendorsement' | translate}}</h2> |                 <h2>{{ 'addon.badges.bendorsement' | translate}}</h2> | ||||||
|             </ion-item-divider> |             </ion-item-divider> | ||||||
| @ -159,7 +159,7 @@ | |||||||
|         </ion-item-group> |         </ion-item-group> | ||||||
| 
 | 
 | ||||||
|         <!-- Related badges --> |         <!-- Related badges --> | ||||||
|         <ion-item-group *ngIf="badge.relatedbadges"> |         <ion-item-group *ngIf="badge && badge.relatedbadges"> | ||||||
|             <ion-item-divider> |             <ion-item-divider> | ||||||
|                 <h2>{{ 'addon.badges.relatedbages' | translate}}</h2> |                 <h2>{{ 'addon.badges.relatedbages' | translate}}</h2> | ||||||
|             </ion-item-divider> |             </ion-item-divider> | ||||||
| @ -172,7 +172,7 @@ | |||||||
|         </ion-item-group> |         </ion-item-group> | ||||||
| 
 | 
 | ||||||
|         <!-- Competencies alignment --> |         <!-- Competencies alignment --> | ||||||
|         <ion-item-group *ngIf="badge.competencies"> |         <ion-item-group *ngIf="badge && badge.competencies"> | ||||||
|             <ion-item-divider> |             <ion-item-divider> | ||||||
|                 <h2>{{ 'addon.badges.alignment' | translate}}</h2> |                 <h2>{{ 'addon.badges.alignment' | translate}}</h2> | ||||||
|             </ion-item-divider> |             </ion-item-divider> | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; | |||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||||
| import { AddonBadgesProvider } from '../../providers/badges'; | import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays the list of calendar events. |  * Page that displays the list of calendar events. | ||||||
| @ -38,7 +38,7 @@ export class AddonBadgesIssuedBadgePage { | |||||||
| 
 | 
 | ||||||
|     user: any = {}; |     user: any = {}; | ||||||
|     course: any = {}; |     course: any = {}; | ||||||
|     badge: any = {}; |     badge: AddonBadgesUserBadge; | ||||||
| 
 | 
 | ||||||
|     badgeLoaded = false; |     badgeLoaded = false; | ||||||
|     currentTime = 0; |     currentTime = 0; | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
| 
 | 
 | ||||||
| import { Component, ViewChild } from '@angular/core'; | import { Component, ViewChild } from '@angular/core'; | ||||||
| import { IonicPage, Content, NavParams } from 'ionic-angular'; | import { IonicPage, Content, NavParams } from 'ionic-angular'; | ||||||
| import { AddonBadgesProvider } from '../../providers/badges'; | import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges'; | ||||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| @ -36,7 +36,7 @@ export class AddonBadgesUserBadgesPage { | |||||||
|     userId: number; |     userId: number; | ||||||
| 
 | 
 | ||||||
|     badgesLoaded = false; |     badgesLoaded = false; | ||||||
|     badges = []; |     badges: AddonBadgesUserBadge[] = []; | ||||||
|     currentTime = 0; |     currentTime = 0; | ||||||
|     badgeHash: string; |     badgeHash: string; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreLoggerProvider } from '@providers/logger'; | import { CoreLoggerProvider } from '@providers/logger'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
|  | import { CoreWSExternalWarning } from '@providers/ws'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -70,7 +71,7 @@ export class AddonBadgesProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise to be resolved when the badges are retrieved. |      * @return Promise to be resolved when the badges are retrieved. | ||||||
|      */ |      */ | ||||||
|     getUserBadges(courseId: number, userId: number, siteId?: string): Promise<any> { |     getUserBadges(courseId: number, userId: number, siteId?: string): Promise<AddonBadgesUserBadge[]> { | ||||||
| 
 | 
 | ||||||
|         this.logger.debug('Get badges for course ' + courseId); |         this.logger.debug('Get badges for course ' + courseId); | ||||||
| 
 | 
 | ||||||
| @ -110,3 +111,76 @@ export class AddonBadgesProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_badges_get_user_badges. | ||||||
|  |  */ | ||||||
|  | export type AddonBadgesGetUserBadgesResult = { | ||||||
|  |     badges: AddonBadgesUserBadge[]; // List of badges.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; // List of warnings.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Badge data returned by WS core_badges_get_user_badges. | ||||||
|  |  */ | ||||||
|  | export type AddonBadgesUserBadge = { | ||||||
|  |     id?: number; // Badge id.
 | ||||||
|  |     name: string; // Badge name.
 | ||||||
|  |     description: string; // Badge description.
 | ||||||
|  |     timecreated?: number; // Time created.
 | ||||||
|  |     timemodified?: number; // Time modified.
 | ||||||
|  |     usercreated?: number; // User created.
 | ||||||
|  |     usermodified?: number; // User modified.
 | ||||||
|  |     issuername: string; // Issuer name.
 | ||||||
|  |     issuerurl: string; // Issuer URL.
 | ||||||
|  |     issuercontact: string; // Issuer contact.
 | ||||||
|  |     expiredate?: number; // Expire date.
 | ||||||
|  |     expireperiod?: number; // Expire period.
 | ||||||
|  |     type?: number; // Type.
 | ||||||
|  |     courseid?: number; // Course id.
 | ||||||
|  |     message?: string; // Message.
 | ||||||
|  |     messagesubject?: string; // Message subject.
 | ||||||
|  |     attachment?: number; // Attachment.
 | ||||||
|  |     notification?: number; // @since 3.6. Whether to notify when badge is awarded.
 | ||||||
|  |     nextcron?: number; // @since 3.6. Next cron.
 | ||||||
|  |     status?: number; // Status.
 | ||||||
|  |     issuedid?: number; // Issued id.
 | ||||||
|  |     uniquehash: string; // Unique hash.
 | ||||||
|  |     dateissued: number; // Date issued.
 | ||||||
|  |     dateexpire: number; // Date expire.
 | ||||||
|  |     visible?: number; // Visible.
 | ||||||
|  |     email?: string; // @since 3.6. User email.
 | ||||||
|  |     version?: string; // @since 3.6. Version.
 | ||||||
|  |     language?: string; // @since 3.6. Language.
 | ||||||
|  |     imageauthorname?: string; // @since 3.6. Name of the image author.
 | ||||||
|  |     imageauthoremail?: string; // @since 3.6. Email of the image author.
 | ||||||
|  |     imageauthorurl?: string; // @since 3.6. URL of the image author.
 | ||||||
|  |     imagecaption?: string; // @since 3.6. Caption of the image.
 | ||||||
|  |     badgeurl: string; // Badge URL.
 | ||||||
|  |     endorsement?: { // @since 3.6.
 | ||||||
|  |         id: number; // Endorsement id.
 | ||||||
|  |         badgeid: number; // Badge id.
 | ||||||
|  |         issuername: string; // Endorsement issuer name.
 | ||||||
|  |         issuerurl: string; // Endorsement issuer URL.
 | ||||||
|  |         issueremail: string; // Endorsement issuer email.
 | ||||||
|  |         claimid: string; // Claim URL.
 | ||||||
|  |         claimcomment: string; // Claim comment.
 | ||||||
|  |         dateissued: number; // Date issued.
 | ||||||
|  |     }; | ||||||
|  |     alignment: { // @since 3.6. Badge alignments.
 | ||||||
|  |         id?: number; // Alignment id.
 | ||||||
|  |         badgeid?: number; // Badge id.
 | ||||||
|  |         targetName?: string; // Target name.
 | ||||||
|  |         targetUrl?: string; // Target URL.
 | ||||||
|  |         targetDescription?: string; // Target description.
 | ||||||
|  |         targetFramework?: string; // Target framework.
 | ||||||
|  |         targetCode?: string; // Target code.
 | ||||||
|  |     }[]; | ||||||
|  |     relatedbadges: { // @since 3.6. Related badges.
 | ||||||
|  |         id: number; // Badge id.
 | ||||||
|  |         name: string; // Badge name.
 | ||||||
|  |         version?: string; // Version.
 | ||||||
|  |         language?: string; // Language.
 | ||||||
|  |         type?: number; // Type.
 | ||||||
|  |     }[]; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -16,7 +16,9 @@ import { Component, OnInit, Injector, Optional } from '@angular/core'; | |||||||
| import { NavController } from 'ionic-angular'; | import { NavController } from 'ionic-angular'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; | import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; | ||||||
| import { AddonBlockRecentlyAccessedItemsProvider } from '../../providers/recentlyaccesseditems'; | import { | ||||||
|  |     AddonBlockRecentlyAccessedItemsProvider, AddonBlockRecentlyAccessedItemsItem | ||||||
|  | } from '../../providers/recentlyaccesseditems'; | ||||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||||
| import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; | import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; | ||||||
| 
 | 
 | ||||||
| @ -28,7 +30,7 @@ import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/hel | |||||||
|     templateUrl: 'addon-block-recentlyaccesseditems.html' |     templateUrl: 'addon-block-recentlyaccesseditems.html' | ||||||
| }) | }) | ||||||
| export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseComponent implements OnInit { | export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseComponent implements OnInit { | ||||||
|     items = []; |     items: AddonBlockRecentlyAccessedItemsItem[] = []; | ||||||
| 
 | 
 | ||||||
|     protected fetchContentDefaultError = 'Error getting recently accessed items data.'; |     protected fetchContentDefaultError = 'Error getting recently accessed items data.'; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -42,14 +42,16 @@ export class AddonBlockRecentlyAccessedItemsProvider { | |||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Promise resolved when the info is retrieved. |      * @return Promise resolved when the info is retrieved. | ||||||
|      */ |      */ | ||||||
|     getRecentItems(siteId?: string): Promise<any[]> { |     getRecentItems(siteId?: string): Promise<AddonBlockRecentlyAccessedItemsItem[]> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const preSets = { |             const preSets = { | ||||||
|                     cacheKey: this.getRecentItemsCacheKey() |                     cacheKey: this.getRecentItemsCacheKey() | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets).then((items) => { |             return site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets) | ||||||
|  |                     .then((items: AddonBlockRecentlyAccessedItemsItem[]) => { | ||||||
|  | 
 | ||||||
|                 return items.map((item) => { |                 return items.map((item) => { | ||||||
|                     const modicon = item.icon && this.domUtils.getHTMLElementAttribute(item.icon, 'src'); |                     const modicon = item.icon && this.domUtils.getHTMLElementAttribute(item.icon, 'src'); | ||||||
|                     item.iconUrl = this.courseProvider.getModuleIconSrc(item.modname, modicon); |                     item.iconUrl = this.courseProvider.getModuleIconSrc(item.modname, modicon); | ||||||
| @ -72,3 +74,27 @@ export class AddonBlockRecentlyAccessedItemsProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS block_recentlyaccesseditems_get_recent_items. | ||||||
|  |  */ | ||||||
|  | export type AddonBlockRecentlyAccessedItemsItem = { | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     courseid: number; // Courseid.
 | ||||||
|  |     cmid: number; // Cmid.
 | ||||||
|  |     userid: number; // Userid.
 | ||||||
|  |     modname: string; // Modname.
 | ||||||
|  |     name: string; // Name.
 | ||||||
|  |     coursename: string; // Coursename.
 | ||||||
|  |     timeaccess: number; // Timeaccess.
 | ||||||
|  |     viewurl: string; // Viewurl.
 | ||||||
|  |     courseviewurl: string; // Courseviewurl.
 | ||||||
|  |     icon: string; // Icon.
 | ||||||
|  | } & AddonBlockRecentlyAccessedItemsItemCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Calculated data for recently accessed item. | ||||||
|  |  */ | ||||||
|  | export type AddonBlockRecentlyAccessedItemsItemCalculatedData = { | ||||||
|  |     iconUrl: string; // Icon URL. Calculated by the app.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; | |||||||
| import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; | import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; | ||||||
| import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; | import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; | ||||||
| import { AddonBlockTimelineProvider } from '../../providers/timeline'; | import { AddonBlockTimelineProvider } from '../../providers/timeline'; | ||||||
|  | import { AddonCalendarEvent } from '@addon/calendar/providers/calendar'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Component to render a timeline block. |  * Component to render a timeline block. | ||||||
| @ -34,9 +35,9 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen | |||||||
|     filter = 'next30days'; |     filter = 'next30days'; | ||||||
|     currentSite: any; |     currentSite: any; | ||||||
|     timeline = { |     timeline = { | ||||||
|         events: [], |         events: <AddonCalendarEvent[]> [], | ||||||
|         loaded: false, |         loaded: false, | ||||||
|         canLoadMore: undefined |         canLoadMore: <number> undefined | ||||||
|     }; |     }; | ||||||
|     timelineCourses = { |     timelineCourses = { | ||||||
|         courses: [], |         courses: [], | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreCoursesDashboardProvider } from '@core/courses/providers/dashboard'; | import { CoreCoursesDashboardProvider } from '@core/courses/providers/dashboard'; | ||||||
|  | import { AddonCalendarEvents, AddonCalendarEventsGroupedByCourse, AddonCalendarEvent } from '@addon/calendar/providers/calendar'; | ||||||
| import * as moment from 'moment'; | import * as moment from 'moment'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -38,7 +39,7 @@ export class AddonBlockTimelineProvider { | |||||||
|      * @return Promise resolved when the info is retrieved. |      * @return Promise resolved when the info is retrieved. | ||||||
|      */ |      */ | ||||||
|     getActionEventsByCourse(courseId: number, afterEventId?: number, siteId?: string): |     getActionEventsByCourse(courseId: number, afterEventId?: number, siteId?: string): | ||||||
|             Promise<{ events: any[], canLoadMore: number }> { |             Promise<{ events: AddonCalendarEvent[], canLoadMore: number }> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 |             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 | ||||||
| @ -55,7 +56,9 @@ export class AddonBlockTimelineProvider { | |||||||
|                 data.aftereventid = afterEventId; |                 data.aftereventid = afterEventId; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('core_calendar_get_action_events_by_course', data, preSets).then((courseEvents): any => { |             return site.read('core_calendar_get_action_events_by_course', data, preSets) | ||||||
|  |                     .then((courseEvents: AddonCalendarEvents): any => { | ||||||
|  | 
 | ||||||
|                 if (courseEvents && courseEvents.events) { |                 if (courseEvents && courseEvents.events) { | ||||||
|                     return this.treatCourseEvents(courseEvents, time); |                     return this.treatCourseEvents(courseEvents, time); | ||||||
|                 } |                 } | ||||||
| @ -82,8 +85,9 @@ export class AddonBlockTimelineProvider { | |||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Promise resolved when the info is retrieved. |      * @return Promise resolved when the info is retrieved. | ||||||
|      */ |      */ | ||||||
|     getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [s: string]: |     getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [courseId: string]: | ||||||
|             { events: any[], canLoadMore: number } }> { |             { events: AddonCalendarEvent[], canLoadMore: number } }> { | ||||||
|  | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 |             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 | ||||||
|                 data = { |                 data = { | ||||||
| @ -95,7 +99,9 @@ export class AddonBlockTimelineProvider { | |||||||
|                     cacheKey: this.getActionEventsByCoursesCacheKey() |                     cacheKey: this.getActionEventsByCoursesCacheKey() | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_calendar_get_action_events_by_courses', data, preSets).then((events): any => { |             return site.read('core_calendar_get_action_events_by_courses', data, preSets) | ||||||
|  |                     .then((events: AddonCalendarEventsGroupedByCourse): any => { | ||||||
|  | 
 | ||||||
|                 if (events && events.groupedbycourse) { |                 if (events && events.groupedbycourse) { | ||||||
|                     const courseEvents = {}; |                     const courseEvents = {}; | ||||||
| 
 | 
 | ||||||
| @ -127,7 +133,9 @@ export class AddonBlockTimelineProvider { | |||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Promise resolved when the info is retrieved. |      * @return Promise resolved when the info is retrieved. | ||||||
|      */ |      */ | ||||||
|     getActionEventsByTimesort(afterEventId: number, siteId?: string): Promise<{ events: any[], canLoadMore: number }> { |     getActionEventsByTimesort(afterEventId: number, siteId?: string): | ||||||
|  |             Promise<{ events: AddonCalendarEvent[], canLoadMore: number }> { | ||||||
|  | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 |             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 | ||||||
|                 data: any = { |                 data: any = { | ||||||
| @ -144,12 +152,14 @@ export class AddonBlockTimelineProvider { | |||||||
|                 data.aftereventid = afterEventId; |                 data.aftereventid = afterEventId; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('core_calendar_get_action_events_by_timesort', data, preSets).then((events): any => { |             return site.read('core_calendar_get_action_events_by_timesort', data, preSets) | ||||||
|                 if (events && events.events) { |                     .then((result: AddonCalendarEvents): any => { | ||||||
|                     const canLoadMore = events.events.length >= data.limitnum ? events.lastid : undefined; | 
 | ||||||
|  |                 if (result && result.events) { | ||||||
|  |                     const canLoadMore = result.events.length >= data.limitnum ? result.lastid : undefined; | ||||||
| 
 | 
 | ||||||
|                     // Filter events by time in case it uses cache.
 |                     // Filter events by time in case it uses cache.
 | ||||||
|                     events = events.events.filter((element) => { |                     const events = result.events.filter((element) => { | ||||||
|                         return element.timesort >= time; |                         return element.timesort >= time; | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
| @ -236,7 +246,9 @@ export class AddonBlockTimelineProvider { | |||||||
|      * @param timeFrom Current time to filter events from. |      * @param timeFrom Current time to filter events from. | ||||||
|      * @return Object with course events and last loaded event id if more can be loaded. |      * @return Object with course events and last loaded event id if more can be loaded. | ||||||
|      */ |      */ | ||||||
|     protected treatCourseEvents(course: any, timeFrom: number): { events: any[], canLoadMore: number } { |     protected treatCourseEvents(course: AddonCalendarEvents, timeFrom: number): | ||||||
|  |             { events: AddonCalendarEvent[], canLoadMore: number } { | ||||||
|  | 
 | ||||||
|         const canLoadMore: number = |         const canLoadMore: number = | ||||||
|             course.events.length >= AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE ? course.lastid : undefined; |             course.events.length >= AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE ? course.lastid : undefined; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; | |||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { AddonBlogProvider } from '../../providers/blog'; | import { AddonBlogProvider, AddonBlogPost } from '../../providers/blog'; | ||||||
| import { CoreCommentsProvider } from '@core/comments/providers/comments'; | import { CoreCommentsProvider } from '@core/comments/providers/comments'; | ||||||
| import { CoreTagProvider } from '@core/tag/providers/tag'; | import { CoreTagProvider } from '@core/tag/providers/tag'; | ||||||
| 
 | 
 | ||||||
| @ -48,7 +48,7 @@ export class AddonBlogEntriesComponent implements OnInit { | |||||||
|     loaded = false; |     loaded = false; | ||||||
|     canLoadMore = false; |     canLoadMore = false; | ||||||
|     loadMoreError = false; |     loadMoreError = false; | ||||||
|     entries = []; |     entries: AddonBlogPostFormatted[] = []; | ||||||
|     currentUserId: number; |     currentUserId: number; | ||||||
|     showMyEntriesToggle = false; |     showMyEntriesToggle = false; | ||||||
|     onlyMyEntries = false; |     onlyMyEntries = false; | ||||||
| @ -118,7 +118,7 @@ export class AddonBlogEntriesComponent implements OnInit { | |||||||
|         const loadPage = this.onlyMyEntries ? this.userPageLoaded : this.pageLoaded; |         const loadPage = this.onlyMyEntries ? this.userPageLoaded : this.pageLoaded; | ||||||
| 
 | 
 | ||||||
|         return this.blogProvider.getEntries(this.filter, loadPage).then((result) => { |         return this.blogProvider.getEntries(this.filter, loadPage).then((result) => { | ||||||
|             const promises = result.entries.map((entry) => { |             const promises = result.entries.map((entry: AddonBlogPostFormatted) => { | ||||||
|                 switch (entry.publishstate) { |                 switch (entry.publishstate) { | ||||||
|                     case 'draft': |                     case 'draft': | ||||||
|                         entry.publishTranslated = 'publishtonoone'; |                         entry.publishTranslated = 'publishtonoone'; | ||||||
| @ -237,5 +237,12 @@ export class AddonBlogEntriesComponent implements OnInit { | |||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Blog post with some calculated data. | ||||||
|  |  */ | ||||||
|  | type AddonBlogPostFormatted = AddonBlogPost & { | ||||||
|  |     publishTranslated?: string; // Calculated in the app. Key of the string to translate the publish state of the post.
 | ||||||
|  |     user?: any; // Calculated in the app. Data of the user that wrote the post.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -18,6 +18,8 @@ import { CoreSitesProvider } from '@providers/sites'; | |||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
|  | import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; | ||||||
|  | import { CoreTagItem } from '@core/tag/providers/tag'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to handle blog entries. |  * Service to handle blog entries. | ||||||
| @ -68,7 +70,7 @@ export class AddonBlogProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise to be resolved when the entries are retrieved. |      * @return Promise to be resolved when the entries are retrieved. | ||||||
|      */ |      */ | ||||||
|     getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise<any> { |     getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise<AddonBlogGetEntriesResult> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const data = { |             const data = { | ||||||
|                 filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value'), |                 filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value'), | ||||||
| @ -105,7 +107,7 @@ export class AddonBlogProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise to be resolved when done. |      * @return Promise to be resolved when done. | ||||||
|      */ |      */ | ||||||
|     logView(filter: any = {}, siteId?: string): Promise<any> { |     logView(filter: any = {}, siteId?: string): Promise<AddonBlogViewEntriesResult> { | ||||||
|         this.pushNotificationsProvider.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId); |         this.pushNotificationsProvider.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId); | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| @ -117,3 +119,48 @@ export class AddonBlogProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by blog's post_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonBlogPost = { | ||||||
|  |     id: number; // Post/entry id.
 | ||||||
|  |     module: string; // Where it was published the post (blog, blog_external...).
 | ||||||
|  |     userid: number; // Post author.
 | ||||||
|  |     courseid: number; // Course where the post was created.
 | ||||||
|  |     groupid: number; // Group post was created for.
 | ||||||
|  |     moduleid: number; // Module id where the post was created (not used anymore).
 | ||||||
|  |     coursemoduleid: number; // Course module id where the post was created.
 | ||||||
|  |     subject: string; // Post subject.
 | ||||||
|  |     summary: string; // Post summary.
 | ||||||
|  |     summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     content: string; // Post content.
 | ||||||
|  |     uniquehash: string; // Post unique hash.
 | ||||||
|  |     rating: number; // Post rating.
 | ||||||
|  |     format: number; // Post content format.
 | ||||||
|  |     attachment: string; // Post atachment.
 | ||||||
|  |     publishstate: string; // Post publish state.
 | ||||||
|  |     lastmodified: number; // When it was last modified.
 | ||||||
|  |     created: number; // When it was created.
 | ||||||
|  |     usermodified: number; // User that updated the post.
 | ||||||
|  |     summaryfiles: CoreWSExternalFile[]; // Summaryfiles.
 | ||||||
|  |     attachmentfiles?: CoreWSExternalFile[]; // Attachmentfiles.
 | ||||||
|  |     tags?: CoreTagItem[]; // @since 3.7. Tags.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_blog_get_entries. | ||||||
|  |  */ | ||||||
|  | export type AddonBlogGetEntriesResult = { | ||||||
|  |     entries: AddonBlogPost[]; | ||||||
|  |     totalentries: number; // The total number of entries found.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_blog_view_entries. | ||||||
|  |  */ | ||||||
|  | export type AddonBlogViewEntriesResult = { | ||||||
|  |     status: boolean; // Status: true if success.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; | |||||||
| import { CoreCourseHelperProvider } from '@core/course/providers/helper'; | import { CoreCourseHelperProvider } from '@core/course/providers/helper'; | ||||||
| import { AddonBlogEntriesComponent } from '../components/entries/entries'; | import { AddonBlogEntriesComponent } from '../components/entries/entries'; | ||||||
| import { AddonBlogProvider } from './blog'; | import { AddonBlogProvider } from './blog'; | ||||||
|  | import { CoreWSExternalFile } from '@providers/ws'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Course nav handler. |  * Course nav handler. | ||||||
| @ -100,7 +101,7 @@ export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler { | |||||||
| 
 | 
 | ||||||
|         return this.blogProvider.getEntries({courseid: course.id}).then((result) => { |         return this.blogProvider.getEntries({courseid: course.id}).then((result) => { | ||||||
|             return result.entries.map((entry) => { |             return result.entries.map((entry) => { | ||||||
|                 let files = []; |                 let files: CoreWSExternalFile[] = []; | ||||||
| 
 | 
 | ||||||
|                 if (entry.attachmentfiles && entry.attachmentfiles.length) { |                 if (entry.attachmentfiles && entry.attachmentfiles.length) { | ||||||
|                     files = entry.attachmentfiles; |                     files = entry.attachmentfiles; | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import { CoreSitesProvider } from '@providers/sites'; | |||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | import { AddonCalendarProvider, AddonCalendarWeek } from '../../providers/calendar'; | ||||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||||
| @ -44,7 +44,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest | |||||||
| 
 | 
 | ||||||
|     periodName: string; |     periodName: string; | ||||||
|     weekDays: any[]; |     weekDays: any[]; | ||||||
|     weeks: any[]; |     weeks: AddonCalendarWeek[]; | ||||||
|     loaded = false; |     loaded = false; | ||||||
|     timeFormat: string; |     timeFormat: string; | ||||||
|     isCurrentMonth: boolean; |     isCurrentMonth: boolean; | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ | |||||||
|         <ng-container *ngFor="let event of filteredEvents"> |         <ng-container *ngFor="let event of filteredEvents"> | ||||||
|             <a ion-item text-wrap [title]="event.name" (click)="eventClicked(event)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> |             <a ion-item text-wrap [title]="event.name" (click)="eventClicked(event)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> | ||||||
|                 <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> |                 <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> | ||||||
|                 <core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> |                 <core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||||
|                 <h2><core-format-text [text]="event.name"></core-format-text></h2> |                 <h2><core-format-text [text]="event.name"></core-format-text></h2> | ||||||
|                 <p><core-format-text [text]="event.formattedtime"></core-format-text></p> |                 <p><core-format-text [text]="event.formattedtime"></core-format-text></p> | ||||||
|                 <ion-note *ngIf="event.offline && !event.deleted" item-end> |                 <ion-note *ngIf="event.offline && !event.deleted" item-end> | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ import { CoreEventsProvider } from '@providers/events'; | |||||||
| import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; | import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | import { AddonCalendarProvider, AddonCalendarCalendarEvent } from '../../providers/calendar'; | ||||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||||
| @ -43,8 +43,8 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, | |||||||
|     protected categoriesRetrieved = false; |     protected categoriesRetrieved = false; | ||||||
|     protected categories = {}; |     protected categories = {}; | ||||||
|     protected currentSiteId: string; |     protected currentSiteId: string; | ||||||
|     protected events = []; // Events (both online and offline).
 |     protected events: AddonCalendarCalendarEvent[] = []; // Events (both online and offline).
 | ||||||
|     protected onlineEvents = []; |     protected onlineEvents: AddonCalendarCalendarEvent[] = []; | ||||||
|     protected offlineEvents = []; // Offline events.
 |     protected offlineEvents = []; // Offline events.
 | ||||||
|     protected deletedEvents = []; // Events deleted in offline.
 |     protected deletedEvents = []; // Events deleted in offline.
 | ||||||
|     protected lookAhead: number; |     protected lookAhead: number; | ||||||
|  | |||||||
| @ -50,7 +50,7 @@ | |||||||
|             <ng-container *ngFor="let event of filteredEvents"> |             <ng-container *ngFor="let event of filteredEvents"> | ||||||
|                 <ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.item-dimmed]="event.ispast" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> |                 <ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.item-dimmed]="event.ispast" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> | ||||||
|                     <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> |                     <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> | ||||||
|                     <core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> |                     <core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||||
|                     <h2><core-format-text [text]="event.name"></core-format-text></h2> |                     <h2><core-format-text [text]="event.name"></core-format-text></h2> | ||||||
|                     <p><core-format-text [text]="event.formattedtime"></core-format-text></p> |                     <p><core-format-text [text]="event.formattedtime"></core-format-text></p> | ||||||
|                     <ion-note *ngIf="event.offline && !event.deleted" item-end> |                     <ion-note *ngIf="event.offline && !event.deleted" item-end> | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; | |||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | import { AddonCalendarProvider, AddonCalendarCalendarEvent } from '../../providers/calendar'; | ||||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||||
| import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; | import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; | ||||||
| @ -45,7 +45,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | |||||||
|     protected day: number; |     protected day: number; | ||||||
|     protected categories = {}; |     protected categories = {}; | ||||||
|     protected events = []; // Events (both online and offline).
 |     protected events = []; // Events (both online and offline).
 | ||||||
|     protected onlineEvents = []; |     protected onlineEvents: AddonCalendarCalendarEvent[] = []; | ||||||
|     protected offlineEvents = {}; // Offline events.
 |     protected offlineEvents = {}; // Offline events.
 | ||||||
|     protected offlineEditedEventsIds = []; // IDs of events edited in offline.
 |     protected offlineEditedEventsIds = []; // IDs of events edited in offline.
 | ||||||
|     protected deletedEvents = []; // Events deleted in offline.
 |     protected deletedEvents = []; // Events deleted in offline.
 | ||||||
| @ -287,7 +287,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | |||||||
|         return this.calendarProvider.getDayEvents(this.year, this.month, this.day).catch((error) => { |         return this.calendarProvider.getDayEvents(this.year, this.month, this.day).catch((error) => { | ||||||
|             if (!this.appProvider.isOnline()) { |             if (!this.appProvider.isOnline()) { | ||||||
|                 // Allow navigating to non-cached days in offline (behave as if using emergency cache).
 |                 // Allow navigating to non-cached days in offline (behave as if using emergency cache).
 | ||||||
|                 return Promise.resolve({ events: [] }); |                 return Promise.resolve({ events: <AddonCalendarCalendarEvent[]> [] }); | ||||||
|             } else { |             } else { | ||||||
|                 return Promise.reject(error); |                 return Promise.reject(error); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -134,7 +134,7 @@ | |||||||
|                 <div *ngIf="event && event.repeatid" text-wrap radio-group [formControlName]="'repeateditall'" class="addon-calendar-radio-container"> |                 <div *ngIf="event && event.repeatid" text-wrap radio-group [formControlName]="'repeateditall'" class="addon-calendar-radio-container"> | ||||||
|                     <ion-item class="addon-calendar-radio-title"><h2>{{ 'addon.calendar.repeatedevents' | translate }}</h2></ion-item> |                     <ion-item class="addon-calendar-radio-title"><h2>{{ 'addon.calendar.repeatedevents' | translate }}</h2></ion-item> | ||||||
|                     <ion-item> |                     <ion-item> | ||||||
|                         <ion-label>{{ 'addon.calendar.repeateditall' | translate:{$a: event.othereventscount} }}</ion-label> |                         <ion-label>{{ 'addon.calendar.repeateditall' | translate:{$a: otherEventsCount} }}</ion-label> | ||||||
|                         <ion-radio [value]="1"></ion-radio> |                         <ion-radio [value]="1"></ion-radio> | ||||||
|                     </ion-item> |                     </ion-item> | ||||||
|                     <ion-item> |                     <ion-item> | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; | |||||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||||
| import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts'; | import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts'; | ||||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | import { AddonCalendarProvider, AddonCalendarGetAccessInfoResult, AddonCalendarEvent } from '../../providers/calendar'; | ||||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||||
| import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; | import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; | ||||||
| @ -58,7 +58,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | |||||||
|     courseGroupSet = false; |     courseGroupSet = false; | ||||||
|     advanced = false; |     advanced = false; | ||||||
|     errors: any; |     errors: any; | ||||||
|     event: any; // The event object (when editing an event).
 |     event: AddonCalendarEvent; // The event object (when editing an event).
 | ||||||
|  |     otherEventsCount: number; | ||||||
| 
 | 
 | ||||||
|     // Form variables.
 |     // Form variables.
 | ||||||
|     eventForm: FormGroup; |     eventForm: FormGroup; | ||||||
| @ -70,7 +71,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | |||||||
|     protected courseId: number; |     protected courseId: number; | ||||||
|     protected originalData: any; |     protected originalData: any; | ||||||
|     protected currentSite: CoreSite; |     protected currentSite: CoreSite; | ||||||
|     protected types: any; // Object with the supported types.
 |     protected types: {[name: string]: boolean}; // Object with the supported types.
 | ||||||
|     protected showAll: boolean; |     protected showAll: boolean; | ||||||
|     protected isDestroyed = false; |     protected isDestroyed = false; | ||||||
|     protected error = false; |     protected error = false; | ||||||
| @ -152,7 +153,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected fetchData(refresh?: boolean): Promise<any> { |     protected fetchData(refresh?: boolean): Promise<any> { | ||||||
|         let accessInfo; |         let accessInfo: AddonCalendarGetAccessInfoResult; | ||||||
| 
 | 
 | ||||||
|         this.error = false; |         this.error = false; | ||||||
| 
 | 
 | ||||||
| @ -197,7 +198,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | |||||||
|                         promises.push(this.calendarProvider.getEventById(this.eventId).then((event) => { |                         promises.push(this.calendarProvider.getEventById(this.eventId).then((event) => { | ||||||
|                             this.event = event; |                             this.event = event; | ||||||
|                             if (event && event.repeatid) { |                             if (event && event.repeatid) { | ||||||
|                                 event.othereventscount = event.eventcount ? event.eventcount - 1 : ''; |                                 this.otherEventsCount = event.eventcount ? event.eventcount - 1 : 0; | ||||||
|                             } |                             } | ||||||
| 
 | 
 | ||||||
|                             return event; |                             return event; | ||||||
| @ -489,7 +490,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|         // Send the data.
 |         // Send the data.
 | ||||||
|         const modal = this.domUtils.showModalLoading('core.sending', true); |         const modal = this.domUtils.showModalLoading('core.sending', true); | ||||||
|         let event; |         let event: AddonCalendarEvent; | ||||||
| 
 | 
 | ||||||
|         this.calendarProvider.submitEvent(this.eventId, data).then((result) => { |         this.calendarProvider.submitEvent(this.eventId, data).then((result) => { | ||||||
|             event = result.event; |             event = result.event; | ||||||
| @ -497,7 +498,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | |||||||
|             if (result.sent) { |             if (result.sent) { | ||||||
|                 // Event created or edited, invalidate right days & months.
 |                 // Event created or edited, invalidate right days & months.
 | ||||||
|                 const numberOfRepetitions = formData.repeat ? formData.repeats : |                 const numberOfRepetitions = formData.repeat ? formData.repeats : | ||||||
|                     (data.repeateditall && this.event.othereventscount ? this.event.othereventscount + 1 : 1); |                     (data.repeateditall && this.otherEventsCount ? this.otherEventsCount + 1 : 1); | ||||||
| 
 | 
 | ||||||
|                 return this.calendarHelper.refreshAfterChangeEvent(result.event, numberOfRepetitions).catch(() => { |                 return this.calendarHelper.refreshAfterChangeEvent(result.event, numberOfRepetitions).catch(() => { | ||||||
|                     // Ignore errors.
 |                     // Ignore errors.
 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
|     <ion-navbar core-back-button> |     <ion-navbar core-back-button> | ||||||
|         <ion-title> |         <ion-title> | ||||||
|             <img *ngIf="event && event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon"> |             <img *ngIf="event && event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon"> | ||||||
|             <core-icon *ngIf="event && event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> |             <core-icon *ngIf="event && event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||||
|             <core-format-text *ngIf="event" [text]="event.name"></core-format-text> |             <core-format-text *ngIf="event" [text]="event.name"></core-format-text> | ||||||
|         </ion-title> |         </ion-title> | ||||||
|         <ion-buttons end> |         <ion-buttons end> | ||||||
| @ -32,7 +32,7 @@ | |||||||
|             <ion-card-content *ngIf="event"> |             <ion-card-content *ngIf="event"> | ||||||
|                 <ion-item text-wrap *ngIf="isSplitViewOn"> |                 <ion-item text-wrap *ngIf="isSplitViewOn"> | ||||||
|                     <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start alt="" role="presentation" class="core-module-icon"> |                     <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start alt="" role="presentation" class="core-module-icon"> | ||||||
|                     <core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> |                     <core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||||
|                     <h2>{{ 'addon.calendar.eventname' | translate }}</h2> |                     <h2>{{ 'addon.calendar.eventname' | translate }}</h2> | ||||||
|                     <p><core-format-text [text]="event.name"></core-format-text></p> |                     <p><core-format-text [text]="event.name"></core-format-text></p> | ||||||
|                     <ion-note item-end *ngIf="event.deleted"> |                     <ion-note item-end *ngIf="event.deleted"> | ||||||
|  | |||||||
| @ -34,7 +34,7 @@ | |||||||
|                     </ion-item-divider> |                     </ion-item-divider> | ||||||
|                     <a ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> |                     <a ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> | ||||||
|                         <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> |                         <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> | ||||||
|                         <core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> |                         <core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||||
|                         <h2><core-format-text [text]="event.name"></core-format-text></h2> |                         <h2><core-format-text [text]="event.name"></core-format-text></h2> | ||||||
|                         <p> |                         <p> | ||||||
|                             {{ event.timestart * 1000 | coreFormatDate: "strftimetime" }} |                             {{ event.timestart * 1000 | coreFormatDate: "strftimetime" }} | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
| 
 | 
 | ||||||
| import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core'; | import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core'; | ||||||
| import { IonicPage, Content, NavParams, NavController } from 'ionic-angular'; | import { IonicPage, Content, NavParams, NavController } from 'ionic-angular'; | ||||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | import { AddonCalendarProvider, AddonCalendarGetEventsEvent } from '../../providers/calendar'; | ||||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||||
| import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; | import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; | ||||||
| @ -62,7 +62,7 @@ export class AddonCalendarListPage implements OnDestroy { | |||||||
|     protected manualSyncObserver: any; |     protected manualSyncObserver: any; | ||||||
|     protected onlineObserver: any; |     protected onlineObserver: any; | ||||||
|     protected currentSiteId: string; |     protected currentSiteId: string; | ||||||
|     protected onlineEvents = []; |     protected onlineEvents: AddonCalendarGetEventsEvent[] = []; | ||||||
|     protected offlineEvents = []; |     protected offlineEvents = []; | ||||||
|     protected deletedEvents = []; |     protected deletedEvents = []; | ||||||
| 
 | 
 | ||||||
| @ -70,7 +70,7 @@ export class AddonCalendarListPage implements OnDestroy { | |||||||
|     eventsLoaded = false; |     eventsLoaded = false; | ||||||
|     events = []; // Events (both online and offline).
 |     events = []; // Events (both online and offline).
 | ||||||
|     notificationsEnabled = false; |     notificationsEnabled = false; | ||||||
|     filteredEvents = []; |     filteredEvents: AddonCalendarGetEventsEvent[] = []; | ||||||
|     canLoadMore = false; |     canLoadMore = false; | ||||||
|     loadMoreError = false; |     loadMoreError = false; | ||||||
|     courseId: number; |     courseId: number; | ||||||
| @ -402,7 +402,7 @@ export class AddonCalendarListPage implements OnDestroy { | |||||||
|      * |      * | ||||||
|      * @return Filtered events. |      * @return Filtered events. | ||||||
|      */ |      */ | ||||||
|     protected getFilteredEvents(): any[] { |     protected getFilteredEvents(): AddonCalendarGetEventsEvent[] { | ||||||
|         if (!this.courseId) { |         if (!this.courseId) { | ||||||
|             // No filter, display everything.
 |             // No filter, display everything.
 | ||||||
|             return this.events; |             return this.events; | ||||||
| @ -581,7 +581,7 @@ export class AddonCalendarListPage implements OnDestroy { | |||||||
|      * @param event Event info. |      * @param event Event info. | ||||||
|      * @return If date has changed and should be shown. |      * @return If date has changed and should be shown. | ||||||
|      */ |      */ | ||||||
|     protected endsSameDay(event: any): boolean { |     protected endsSameDay(event: AddonCalendarGetEventsEvent): boolean { | ||||||
|         if (!event.timeduration) { |         if (!event.timeduration) { | ||||||
|             // No duration.
 |             // No duration.
 | ||||||
|             return true; |             return true; | ||||||
|  | |||||||
| @ -31,6 +31,7 @@ import { SQLiteDB } from '@classes/sqlitedb'; | |||||||
| import { AddonCalendarOfflineProvider } from './calendar-offline'; | import { AddonCalendarOfflineProvider } from './calendar-offline'; | ||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { TranslateService } from '@ngx-translate/core'; | import { TranslateService } from '@ngx-translate/core'; | ||||||
|  | import { CoreWSExternalWarning, CoreWSDate } from '@providers/ws'; | ||||||
| import * as moment from 'moment'; | import * as moment from 'moment'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -489,7 +490,7 @@ export class AddonCalendarProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     deleteEventOnline(eventId: number, deleteAll?: boolean, siteId?: string): Promise<any> { |     deleteEventOnline(eventId: number, deleteAll?: boolean, siteId?: string): Promise<null> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| 
 | 
 | ||||||
|             const params = { |             const params = { | ||||||
| @ -535,22 +536,6 @@ export class AddonCalendarProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check if event ends the same day or not. |  | ||||||
|      * |  | ||||||
|      * @param event Event info. |  | ||||||
|      * @return If the . |  | ||||||
|      */ |  | ||||||
|     endsSameDay(event: any): boolean { |  | ||||||
|         if (!event.timeduration) { |  | ||||||
|             // No duration.
 |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Check if day has changed.
 |  | ||||||
|         return moment(event.timestart * 1000).isSame((event.timestart + event.timeduration) * 1000, 'day'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Format event time. Similar to calendar_format_event_time. |      * Format event time. Similar to calendar_format_event_time. | ||||||
|      * |      * | ||||||
| @ -562,8 +547,8 @@ export class AddonCalendarProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved with the formatted event time. |      * @return Promise resolved with the formatted event time. | ||||||
|      */ |      */ | ||||||
|     formatEventTime(event: any, format: string, useCommonWords: boolean = true, seenDay?: number, showTime: number = 0, |     formatEventTime(event: AddonCalendarAnyEvent, format: string, useCommonWords: boolean = true, seenDay?: number, | ||||||
|             siteId?: string): Promise<string> { |             showTime: number = 0, siteId?: string): Promise<string> { | ||||||
| 
 | 
 | ||||||
|         const start = event.timestart * 1000, |         const start = event.timestart * 1000, | ||||||
|             end = (event.timestart + event.timeduration) * 1000; |             end = (event.timestart + event.timeduration) * 1000; | ||||||
| @ -635,7 +620,7 @@ export class AddonCalendarProvider { | |||||||
|      * @return Promise resolved with object with access information. |      * @return Promise resolved with object with access information. | ||||||
|      * @since 3.7 |      * @since 3.7 | ||||||
|      */ |      */ | ||||||
|     getAccessInformation(courseId?: number, siteId?: string): Promise<any> { |     getAccessInformation(courseId?: number, siteId?: string): Promise<AddonCalendarGetAccessInfoResult> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const params: any = {}, |             const params: any = {}, | ||||||
|                 preSets = { |                 preSets = { | ||||||
| @ -680,7 +665,7 @@ export class AddonCalendarProvider { | |||||||
|      * @return Promise resolved with an object indicating the types. |      * @return Promise resolved with an object indicating the types. | ||||||
|      * @since 3.7 |      * @since 3.7 | ||||||
|      */ |      */ | ||||||
|     getAllowedEventTypes(courseId?: number, siteId?: string): Promise<any> { |     getAllowedEventTypes(courseId?: number, siteId?: string): Promise<{[name: string]: boolean}> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const params: any = {}, |             const params: any = {}, | ||||||
|                 preSets = { |                 preSets = { | ||||||
| @ -691,7 +676,8 @@ export class AddonCalendarProvider { | |||||||
|                 params.courseid = courseId; |                 params.courseid = courseId; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('core_calendar_get_allowed_event_types', params, preSets).then((response) => { |             return site.read('core_calendar_get_allowed_event_types', params, preSets) | ||||||
|  |                     .then((response: AddonCalendarGetAllowedEventTypesResult) => { | ||||||
|                 // Convert the array to an object.
 |                 // Convert the array to an object.
 | ||||||
|                 const result = {}; |                 const result = {}; | ||||||
| 
 | 
 | ||||||
| @ -812,11 +798,10 @@ export class AddonCalendarProvider { | |||||||
|      * Get a calendar event. If the server request fails and data is not cached, try to get it from local DB. |      * Get a calendar event. If the server request fails and data is not cached, try to get it from local DB. | ||||||
|      * |      * | ||||||
|      * @param id Event ID. |      * @param id Event ID. | ||||||
|      * @param refresh True when we should update the event data. |  | ||||||
|      * @param siteId ID of the site. If not defined, use current site. |      * @param siteId ID of the site. If not defined, use current site. | ||||||
|      * @return Promise resolved when the event data is retrieved. |      * @return Promise resolved when the event data is retrieved. | ||||||
|      */ |      */ | ||||||
|     getEvent(id: number, siteId?: string): Promise<any> { |     getEvent(id: number, siteId?: string): Promise<AddonCalendarGetEventsEvent> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const preSets = { |             const preSets = { | ||||||
|                     cacheKey: this.getEventCacheKey(id), |                     cacheKey: this.getEventCacheKey(id), | ||||||
| @ -834,7 +819,8 @@ export class AddonCalendarProvider { | |||||||
|                     } |                     } | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => { |             return site.read('core_calendar_get_calendar_events', data, preSets) | ||||||
|  |                     .then((response: AddonCalendarGetEventsResult) => { | ||||||
|                 // The WebService returns all category events. Check the response to search for the event we want.
 |                 // The WebService returns all category events. Check the response to search for the event we want.
 | ||||||
|                 const event = response.events.find((e) => { return e.id == id; }); |                 const event = response.events.find((e) => { return e.id == id; }); | ||||||
| 
 | 
 | ||||||
| @ -849,12 +835,11 @@ export class AddonCalendarProvider { | |||||||
|      * Get a calendar event by ID. This function returns more data than getEvent, but it isn't available in all Moodles. |      * Get a calendar event by ID. This function returns more data than getEvent, but it isn't available in all Moodles. | ||||||
|      * |      * | ||||||
|      * @param id Event ID. |      * @param id Event ID. | ||||||
|      * @param refresh True when we should update the event data. |  | ||||||
|      * @param siteId ID of the site. If not defined, use current site. |      * @param siteId ID of the site. If not defined, use current site. | ||||||
|      * @return Promise resolved when the event data is retrieved. |      * @return Promise resolved when the event data is retrieved. | ||||||
|      * @since 3.4 |      * @since 3.4 | ||||||
|      */ |      */ | ||||||
|     getEventById(id: number, siteId?: string): Promise<any> { |     getEventById(id: number, siteId?: string): Promise<AddonCalendarEvent> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const preSets = { |             const preSets = { | ||||||
|                     cacheKey: this.getEventCacheKey(id), |                     cacheKey: this.getEventCacheKey(id), | ||||||
| @ -864,7 +849,8 @@ export class AddonCalendarProvider { | |||||||
|                     eventid: id |                     eventid: id | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_calendar_get_calendar_event_by_id', data, preSets).then((response) => { |             return site.read('core_calendar_get_calendar_event_by_id', data, preSets) | ||||||
|  |                     .then((response: AddonCalendarGetEventByIdResult) => { | ||||||
|                 return response.event; |                 return response.event; | ||||||
|             }).catch((error) => { |             }).catch((error) => { | ||||||
|                 return this.getEventFromLocalDb(id).catch(() => { |                 return this.getEventFromLocalDb(id).catch(() => { | ||||||
| @ -918,7 +904,7 @@ export class AddonCalendarProvider { | |||||||
|      * @param siteId ID of the site the event belongs to. If not defined, use current site. |      * @param siteId ID of the site the event belongs to. If not defined, use current site. | ||||||
|      * @return Promise resolved when the notification is updated. |      * @return Promise resolved when the notification is updated. | ||||||
|      */ |      */ | ||||||
|     addEventReminder(event: any, time: number, siteId?: string): Promise<any> { |     addEventReminder(event: AddonCalendarAnyEvent, time: number, siteId?: string): Promise<any> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const reminder = { |             const reminder = { | ||||||
|                 eventid: event.id, |                 eventid: event.id, | ||||||
| @ -976,7 +962,7 @@ export class AddonCalendarProvider { | |||||||
|      * @return Promise resolved with the response. |      * @return Promise resolved with the response. | ||||||
|      */ |      */ | ||||||
|     getDayEvents(year: number, month: number, day: number, courseId?: number, categoryId?: number, ignoreCache?: boolean, |     getDayEvents(year: number, month: number, day: number, courseId?: number, categoryId?: number, ignoreCache?: boolean, | ||||||
|             siteId?: string): Promise<any> { |             siteId?: string): Promise<AddonCalendarCalendarDay> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| 
 | 
 | ||||||
| @ -1003,7 +989,7 @@ export class AddonCalendarProvider { | |||||||
|                 preSets.emergencyCache = false; |                 preSets.emergencyCache = false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('core_calendar_get_calendar_day_view', data, preSets).then((response) => { |             return site.read('core_calendar_get_calendar_day_view', data, preSets).then((response: AddonCalendarCalendarDay) => { | ||||||
|                 this.storeEventsInLocalDB(response.events, siteId); |                 this.storeEventsInLocalDB(response.events, siteId); | ||||||
| 
 | 
 | ||||||
|                 return response; |                 return response; | ||||||
| @ -1071,10 +1057,10 @@ export class AddonCalendarProvider { | |||||||
|      * @param daysToStart Number of days from now to start getting events. |      * @param daysToStart Number of days from now to start getting events. | ||||||
|      * @param daysInterval Number of days between timestart and timeend. |      * @param daysInterval Number of days between timestart and timeend. | ||||||
|      * @param siteId Site to get the events from. If not defined, use current site. |      * @param siteId Site to get the events from. If not defined, use current site. | ||||||
|      * @return Promise to be resolved when the participants are retrieved. |      * @return Promise to be resolved when the events are retrieved. | ||||||
|      */ |      */ | ||||||
|     getEventsList(initialTime?: number, daysToStart: number = 0, daysInterval: number = AddonCalendarProvider.DAYS_INTERVAL, |     getEventsList(initialTime?: number, daysToStart: number = 0, daysInterval: number = AddonCalendarProvider.DAYS_INTERVAL, | ||||||
|             siteId?: string): Promise<any[]> { |             siteId?: string): Promise<AddonCalendarGetEventsEvent[]> { | ||||||
| 
 | 
 | ||||||
|         initialTime = initialTime || this.timeUtils.timestamp(); |         initialTime = initialTime || this.timeUtils.timestamp(); | ||||||
| 
 | 
 | ||||||
| @ -1122,7 +1108,9 @@ export class AddonCalendarProvider { | |||||||
|                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES |                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|                 return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => { |                 return site.read('core_calendar_get_calendar_events', data, preSets) | ||||||
|  |                         .then((response: AddonCalendarGetEventsResult) => { | ||||||
|  | 
 | ||||||
|                     if (!this.canViewMonthInSite(site)) { |                     if (!this.canViewMonthInSite(site)) { | ||||||
|                         // Store events only in 3.1-3.3. In 3.4+ we'll use the new WS that return more info.
 |                         // Store events only in 3.1-3.3. In 3.4+ we'll use the new WS that return more info.
 | ||||||
|                         this.storeEventsInLocalDB(response.events, siteId); |                         this.storeEventsInLocalDB(response.events, siteId); | ||||||
| @ -1178,7 +1166,7 @@ export class AddonCalendarProvider { | |||||||
|      * @return Promise resolved with the response. |      * @return Promise resolved with the response. | ||||||
|      */ |      */ | ||||||
|     getMonthlyEvents(year: number, month: number, courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string) |     getMonthlyEvents(year: number, month: number, courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string) | ||||||
|             : Promise<any> { |             : Promise<AddonCalendarMonth> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| 
 | 
 | ||||||
| @ -1210,7 +1198,9 @@ export class AddonCalendarProvider { | |||||||
|                 preSets.emergencyCache = false; |                 preSets.emergencyCache = false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('core_calendar_get_calendar_monthly_view', data, preSets).then((response) => { |             return site.read('core_calendar_get_calendar_monthly_view', data, preSets) | ||||||
|  |                     .then((response: AddonCalendarMonth) => { | ||||||
|  | 
 | ||||||
|                 response.weeks.forEach((week) => { |                 response.weeks.forEach((week) => { | ||||||
|                     week.days.forEach((day) => { |                     week.days.forEach((day) => { | ||||||
|                         this.storeEventsInLocalDB(day.events, siteId); |                         this.storeEventsInLocalDB(day.events, siteId); | ||||||
| @ -1270,7 +1260,8 @@ export class AddonCalendarProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved with the response. |      * @return Promise resolved with the response. | ||||||
|      */ |      */ | ||||||
|     getUpcomingEvents(courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string): Promise<any> { |     getUpcomingEvents(courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string) | ||||||
|  |             : Promise<AddonCalendarUpcoming> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| 
 | 
 | ||||||
| @ -1293,7 +1284,7 @@ export class AddonCalendarProvider { | |||||||
|                 preSets.emergencyCache = false; |                 preSets.emergencyCache = false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('core_calendar_get_calendar_upcoming_view', data, preSets).then((response) => { |             return site.read('core_calendar_get_calendar_upcoming_view', data, preSets).then((response: AddonCalendarUpcoming) => { | ||||||
|                 this.storeEventsInLocalDB(response.events, siteId); |                 this.storeEventsInLocalDB(response.events, siteId); | ||||||
| 
 | 
 | ||||||
|                 return response; |                 return response; | ||||||
| @ -1604,11 +1595,14 @@ export class AddonCalendarProvider { | |||||||
|      * If local notification plugin is not enabled, resolve the promise. |      * If local notification plugin is not enabled, resolve the promise. | ||||||
|      * |      * | ||||||
|      * @param event Event to schedule. |      * @param event Event to schedule. | ||||||
|  |      * @param reminderId The reminder ID. | ||||||
|      * @param time Notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start". |      * @param time Notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start". | ||||||
|      * @param siteId Site ID the event belongs to. If not defined, use current site. |      * @param siteId Site ID the event belongs to. If not defined, use current site. | ||||||
|      * @return Promise resolved when the notification is scheduled. |      * @return Promise resolved when the notification is scheduled. | ||||||
|      */ |      */ | ||||||
|     protected scheduleEventNotification(event: any, reminderId: number, time: number, siteId?: string): Promise<void> { |     protected scheduleEventNotification(event: AddonCalendarAnyEvent, reminderId: number, time: number, siteId?: string) | ||||||
|  |             : Promise<void> { | ||||||
|  | 
 | ||||||
|         if (this.localNotificationsProvider.isAvailable()) { |         if (this.localNotificationsProvider.isAvailable()) { | ||||||
|             siteId = siteId || this.sitesProvider.getCurrentSiteId(); |             siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
| 
 | 
 | ||||||
| @ -1672,7 +1666,7 @@ export class AddonCalendarProvider { | |||||||
|      * @param siteId ID of the site the events belong to. If not defined, use current site. |      * @param siteId ID of the site the events belong to. If not defined, use current site. | ||||||
|      * @return Promise resolved when all the notifications have been scheduled. |      * @return Promise resolved when all the notifications have been scheduled. | ||||||
|      */ |      */ | ||||||
|     scheduleEventsNotifications(events: any[], siteId?: string): Promise<any[]> { |     scheduleEventsNotifications(events: AddonCalendarAnyEvent[], siteId?: string): Promise<any[]> { | ||||||
| 
 | 
 | ||||||
|         if (this.localNotificationsProvider.isAvailable()) { |         if (this.localNotificationsProvider.isAvailable()) { | ||||||
|             siteId = siteId || this.sitesProvider.getCurrentSiteId(); |             siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
| @ -1803,11 +1797,10 @@ export class AddonCalendarProvider { | |||||||
|      * @param timeCreated The time the event was created. Only if modifying a new offline event. |      * @param timeCreated The time the event was created. Only if modifying a new offline event. | ||||||
|      * @param forceOffline True to always save it in offline. |      * @param forceOffline True to always save it in offline. | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved with the event and a boolean indicating if data was |      * @return Promise resolved with the event and a boolean indicating if data was sent to server or stored in offline. | ||||||
|      *         sent to server or stored in offline. |  | ||||||
|      */ |      */ | ||||||
|     submitEvent(eventId: number, formData: any, timeCreated?: number, forceOffline?: boolean, siteId?: string): |     submitEvent(eventId: number, formData: any, timeCreated?: number, forceOffline?: boolean, siteId?: string): | ||||||
|             Promise<{sent: boolean, event: any}> { |             Promise<{sent: boolean, event: AddonCalendarEvent}> { | ||||||
| 
 | 
 | ||||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); |         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
| 
 | 
 | ||||||
| @ -1847,7 +1840,7 @@ export class AddonCalendarProvider { | |||||||
|      * @param siteId Site ID. If not provided, current site. |      * @param siteId Site ID. If not provided, current site. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     submitEventOnline(eventId: number, formData: any, siteId?: string): Promise<any> { |     submitEventOnline(eventId: number, formData: any, siteId?: string): Promise<AddonCalendarEvent> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             // Add data that is "hidden" in web.
 |             // Add data that is "hidden" in web.
 | ||||||
|             formData.id = eventId || 0; |             formData.id = eventId || 0; | ||||||
| @ -1865,10 +1858,12 @@ export class AddonCalendarProvider { | |||||||
|                 formdata: this.utils.objectToGetParams(formData) |                 formdata: this.utils.objectToGetParams(formData) | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.write('core_calendar_submit_create_update_form', params).then((result) => { |             return site.write('core_calendar_submit_create_update_form', params) | ||||||
|  |                     .then((result: AddonCalendarSubmitCreateUpdateFormResult): AddonCalendarEvent => { | ||||||
|  | 
 | ||||||
|                 if (result.validationerror) { |                 if (result.validationerror) { | ||||||
|                     // Simulate a WS error.
 |                     // Simulate a WS error.
 | ||||||
|                     return Promise.reject({ |                     return <any> Promise.reject({ | ||||||
|                         message: this.translate.instant('core.invalidformdata'), |                         message: this.translate.instant('core.invalidformdata'), | ||||||
|                         errorcode: 'validationerror' |                         errorcode: 'validationerror' | ||||||
|                     }); |                     }); | ||||||
| @ -1879,3 +1874,337 @@ export class AddonCalendarProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's events_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarEvents = { | ||||||
|  |     events: AddonCalendarEvent[]; // Events.
 | ||||||
|  |     firstid: number; // Firstid.
 | ||||||
|  |     lastid: number; // Lastid.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's events_grouped_by_course_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarEventsGroupedByCourse = { | ||||||
|  |     groupedbycourse: AddonCalendarEventsSameCourse[]; // Groupped by course.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's events_same_course_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarEventsSameCourse = AddonCalendarEvents & { | ||||||
|  |     courseid: number; // Courseid.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's event_exporter_base. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarEventBase = { | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     name: string; // Name.
 | ||||||
|  |     description?: string; // Description.
 | ||||||
|  |     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     location?: string; // Location.
 | ||||||
|  |     categoryid?: number; // Categoryid.
 | ||||||
|  |     groupid?: number; // Groupid.
 | ||||||
|  |     userid?: number; // Userid.
 | ||||||
|  |     repeatid?: number; // Repeatid.
 | ||||||
|  |     eventcount?: number; // Eventcount.
 | ||||||
|  |     modulename?: string; // Modulename.
 | ||||||
|  |     instance?: number; // Instance.
 | ||||||
|  |     eventtype: string; // Eventtype.
 | ||||||
|  |     timestart: number; // Timestart.
 | ||||||
|  |     timeduration: number; // Timeduration.
 | ||||||
|  |     timesort: number; // Timesort.
 | ||||||
|  |     visible: number; // Visible.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     icon: { | ||||||
|  |         key: string; // Key.
 | ||||||
|  |         component: string; // Component.
 | ||||||
|  |         alttext: string; // Alttext.
 | ||||||
|  |     }; | ||||||
|  |     category?: { | ||||||
|  |         id: number; // Id.
 | ||||||
|  |         name: string; // Name.
 | ||||||
|  |         idnumber: string; // Idnumber.
 | ||||||
|  |         description?: string; // Description.
 | ||||||
|  |         parent: number; // Parent.
 | ||||||
|  |         coursecount: number; // Coursecount.
 | ||||||
|  |         visible: number; // Visible.
 | ||||||
|  |         timemodified: number; // Timemodified.
 | ||||||
|  |         depth: number; // Depth.
 | ||||||
|  |         nestedname: string; // Nestedname.
 | ||||||
|  |         url: string; // Url.
 | ||||||
|  |     }; | ||||||
|  |     course?: { | ||||||
|  |         id: number; // Id.
 | ||||||
|  |         fullname: string; // Fullname.
 | ||||||
|  |         shortname: string; // Shortname.
 | ||||||
|  |         idnumber: string; // Idnumber.
 | ||||||
|  |         summary: string; // Summary.
 | ||||||
|  |         summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |         startdate: number; // Startdate.
 | ||||||
|  |         enddate: number; // Enddate.
 | ||||||
|  |         visible: boolean; // Visible.
 | ||||||
|  |         fullnamedisplay: string; // Fullnamedisplay.
 | ||||||
|  |         viewurl: string; // Viewurl.
 | ||||||
|  |         courseimage: string; // Courseimage.
 | ||||||
|  |         progress?: number; // Progress.
 | ||||||
|  |         hasprogress: boolean; // Hasprogress.
 | ||||||
|  |         isfavourite: boolean; // Isfavourite.
 | ||||||
|  |         hidden: boolean; // Hidden.
 | ||||||
|  |         timeaccess?: number; // Timeaccess.
 | ||||||
|  |         showshortname: boolean; // Showshortname.
 | ||||||
|  |         coursecategory: string; // Coursecategory.
 | ||||||
|  |     }; | ||||||
|  |     subscription?: { | ||||||
|  |         displayeventsource: boolean; // Displayeventsource.
 | ||||||
|  |         subscriptionname?: string; // Subscriptionname.
 | ||||||
|  |         subscriptionurl?: string; // Subscriptionurl.
 | ||||||
|  |     }; | ||||||
|  |     canedit: boolean; // Canedit.
 | ||||||
|  |     candelete: boolean; // Candelete.
 | ||||||
|  |     deleteurl: string; // Deleteurl.
 | ||||||
|  |     editurl: string; // Editurl.
 | ||||||
|  |     viewurl: string; // Viewurl.
 | ||||||
|  |     formattedtime: string; // Formattedtime.
 | ||||||
|  |     isactionevent: boolean; // Isactionevent.
 | ||||||
|  |     iscourseevent: boolean; // Iscourseevent.
 | ||||||
|  |     iscategoryevent: boolean; // Iscategoryevent.
 | ||||||
|  |     groupname?: string; // Groupname.
 | ||||||
|  |     normalisedeventtype: string; // Normalisedeventtype.
 | ||||||
|  |     normalisedeventtypetext: string; // Normalisedeventtypetext.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's event_exporter.  Don't confuse it with AddonCalendarCalendarEvent. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarEvent = AddonCalendarEventBase & { | ||||||
|  |     url: string; // Url.
 | ||||||
|  |     action?: { | ||||||
|  |         name: string; // Name.
 | ||||||
|  |         url: string; // Url.
 | ||||||
|  |         itemcount: number; // Itemcount.
 | ||||||
|  |         actionable: boolean; // Actionable.
 | ||||||
|  |         showitemcount: boolean; // Showitemcount.
 | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's calendar_event_exporter. Don't confuse it with AddonCalendarEvent. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarCalendarEvent = AddonCalendarEventBase & { | ||||||
|  |     url: string; // Url.
 | ||||||
|  |     islastday: boolean; // Islastday.
 | ||||||
|  |     popupname: string; // Popupname.
 | ||||||
|  |     mindaytimestamp?: number; // Mindaytimestamp.
 | ||||||
|  |     mindayerror?: string; // Mindayerror.
 | ||||||
|  |     maxdaytimestamp?: number; // Maxdaytimestamp.
 | ||||||
|  |     maxdayerror?: string; // Maxdayerror.
 | ||||||
|  |     draggable: boolean; // Draggable.
 | ||||||
|  | } & AddonCalendarCalendarEventCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Any of the possible types of events. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarAnyEvent = AddonCalendarGetEventsEvent | AddonCalendarEvent | AddonCalendarCalendarEvent; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's calendar_day_exporter. Don't confuse it with AddonCalendarDay. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarCalendarDay = { | ||||||
|  |     events: AddonCalendarCalendarEvent[]; // Events.
 | ||||||
|  |     defaulteventcontext: number; // Defaulteventcontext.
 | ||||||
|  |     filter_selector: string; // Filter_selector.
 | ||||||
|  |     courseid: number; // Courseid.
 | ||||||
|  |     categoryid?: number; // Categoryid.
 | ||||||
|  |     neweventtimestamp: number; // Neweventtimestamp.
 | ||||||
|  |     date: CoreWSDate; | ||||||
|  |     periodname: string; // Periodname.
 | ||||||
|  |     previousperiod: CoreWSDate; | ||||||
|  |     previousperiodlink: string; // Previousperiodlink.
 | ||||||
|  |     previousperiodname: string; // Previousperiodname.
 | ||||||
|  |     nextperiod: CoreWSDate; | ||||||
|  |     nextperiodname: string; // Nextperiodname.
 | ||||||
|  |     nextperiodlink: string; // Nextperiodlink.
 | ||||||
|  |     larrow: string; // Larrow.
 | ||||||
|  |     rarrow: string; // Rarrow.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's month_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarMonth = { | ||||||
|  |     url: string; // Url.
 | ||||||
|  |     courseid: number; // Courseid.
 | ||||||
|  |     categoryid?: number; // Categoryid.
 | ||||||
|  |     filter_selector?: string; // Filter_selector.
 | ||||||
|  |     weeks: AddonCalendarWeek[]; // Weeks.
 | ||||||
|  |     daynames: AddonCalendarDayName[]; // Daynames.
 | ||||||
|  |     view: string; // View.
 | ||||||
|  |     date: CoreWSDate; | ||||||
|  |     periodname: string; // Periodname.
 | ||||||
|  |     includenavigation: boolean; // Includenavigation.
 | ||||||
|  |     initialeventsloaded: boolean; // Initialeventsloaded.
 | ||||||
|  |     previousperiod: CoreWSDate; | ||||||
|  |     previousperiodlink: string; // Previousperiodlink.
 | ||||||
|  |     previousperiodname: string; // Previousperiodname.
 | ||||||
|  |     nextperiod: CoreWSDate; | ||||||
|  |     nextperiodname: string; // Nextperiodname.
 | ||||||
|  |     nextperiodlink: string; // Nextperiodlink.
 | ||||||
|  |     larrow: string; // Larrow.
 | ||||||
|  |     rarrow: string; // Rarrow.
 | ||||||
|  |     defaulteventcontext: number; // Defaulteventcontext.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's week_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarWeek = { | ||||||
|  |     prepadding: number[]; // Prepadding.
 | ||||||
|  |     postpadding: number[]; // Postpadding.
 | ||||||
|  |     days: AddonCalendarWeekDay[]; // Days.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's week_day_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarWeekDay = AddonCalendarDay & { | ||||||
|  |     istoday: boolean; // Istoday.
 | ||||||
|  |     isweekend: boolean; // Isweekend.
 | ||||||
|  |     popovertitle: string; // Popovertitle.
 | ||||||
|  |     ispast?: boolean; // Calculated in the app. Whether the day is in the past.
 | ||||||
|  |     filteredEvents?: AddonCalendarCalendarEvent[]; // Calculated in the app. Filtered events.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's day_exporter. Don't confuse it with AddonCalendarCalendarDay. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarDay = { | ||||||
|  |     seconds: number; // Seconds.
 | ||||||
|  |     minutes: number; // Minutes.
 | ||||||
|  |     hours: number; // Hours.
 | ||||||
|  |     mday: number; // Mday.
 | ||||||
|  |     wday: number; // Wday.
 | ||||||
|  |     year: number; // Year.
 | ||||||
|  |     yday: number; // Yday.
 | ||||||
|  |     timestamp: number; // Timestamp.
 | ||||||
|  |     neweventtimestamp: number; // Neweventtimestamp.
 | ||||||
|  |     viewdaylink?: string; // Viewdaylink.
 | ||||||
|  |     events: AddonCalendarCalendarEvent[]; // Events.
 | ||||||
|  |     hasevents: boolean; // Hasevents.
 | ||||||
|  |     calendareventtypes: string[]; // Calendareventtypes.
 | ||||||
|  |     previousperiod: number; // Previousperiod.
 | ||||||
|  |     nextperiod: number; // Nextperiod.
 | ||||||
|  |     navigation: string; // Navigation.
 | ||||||
|  |     haslastdayofevent: boolean; // Haslastdayofevent.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's day_name_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarDayName = { | ||||||
|  |     dayno: number; // Dayno.
 | ||||||
|  |     shortname: string; // Shortname.
 | ||||||
|  |     fullname: string; // Fullname.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by calendar's calendar_upcoming_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarUpcoming = { | ||||||
|  |     events: AddonCalendarCalendarEvent[]; // Events.
 | ||||||
|  |     defaulteventcontext: number; // Defaulteventcontext.
 | ||||||
|  |     filter_selector: string; // Filter_selector.
 | ||||||
|  |     courseid: number; // Courseid.
 | ||||||
|  |     categoryid?: number; // Categoryid.
 | ||||||
|  |     isloggedin: boolean; // Isloggedin.
 | ||||||
|  |     date: CoreWSDate; // Date.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_calendar_get_calendar_access_information. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarGetAccessInfoResult = { | ||||||
|  |     canmanageentries: boolean; // Whether the user can manage entries.
 | ||||||
|  |     canmanageownentries: boolean; // Whether the user can manage its own entries.
 | ||||||
|  |     canmanagegroupentries: boolean; // Whether the user can manage group entries.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_calendar_get_allowed_event_types. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarGetAllowedEventTypesResult = { | ||||||
|  |     allowedeventtypes: string[]; | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_calendar_get_calendar_events. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarGetEventsResult = { | ||||||
|  |     events: AddonCalendarGetEventsEvent[]; | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Event data returned by WS core_calendar_get_calendar_events. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarGetEventsEvent = { | ||||||
|  |     id: number; // Event id.
 | ||||||
|  |     name: string; // Event name.
 | ||||||
|  |     description?: string; // Description.
 | ||||||
|  |     format: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     courseid: number; // Course id.
 | ||||||
|  |     categoryid?: number; // @since 3.4. Category id (only for category events).
 | ||||||
|  |     groupid: number; // Group id.
 | ||||||
|  |     userid: number; // User id.
 | ||||||
|  |     repeatid: number; // Repeat id.
 | ||||||
|  |     modulename?: string; // Module name.
 | ||||||
|  |     instance: number; // Instance id.
 | ||||||
|  |     eventtype: string; // Event type.
 | ||||||
|  |     timestart: number; // Timestart.
 | ||||||
|  |     timeduration: number; // Time duration.
 | ||||||
|  |     visible: number; // Visible.
 | ||||||
|  |     uuid?: string; // Unique id of ical events.
 | ||||||
|  |     sequence: number; // Sequence.
 | ||||||
|  |     timemodified: number; // Time modified.
 | ||||||
|  |     subscriptionid?: number; // Subscription id.
 | ||||||
|  |     showDate?: boolean; // Calculated in the app. Whether date should be shown before this event.
 | ||||||
|  |     endsSameDay?: boolean; // Calculated in the app. Whether the event finishes the same day it starts.
 | ||||||
|  |     deleted?: boolean; // Calculated in the app. Whether it has been deleted in offline.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_calendar_get_calendar_event_by_id. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarGetEventByIdResult = { | ||||||
|  |     event: AddonCalendarEvent; // Event.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_calendar_submit_create_update_form. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarSubmitCreateUpdateFormResult = { | ||||||
|  |     event?: AddonCalendarEvent; // Event.
 | ||||||
|  |     validationerror: boolean; // Invalid form data.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Calculated data for AddonCalendarCalendarEvent. | ||||||
|  |  */ | ||||||
|  | export type AddonCalendarCalendarEventCalculatedData = { | ||||||
|  |     eventIcon?: string; // Calculated in the app. Event icon.
 | ||||||
|  |     moduleIcon?: string; // Calculated in the app. Module icon.
 | ||||||
|  |     formattedType?: string; // Calculated in the app. Formatted type.
 | ||||||
|  |     duration?: number; // Calculated in the app. Duration of offline event.
 | ||||||
|  |     format?: number; // Calculated in the app. Format of offline event.
 | ||||||
|  |     timedurationuntil?: number; // Calculated in the app. Time duration until of offline event.
 | ||||||
|  |     timedurationminutes?: number; // Calculated in the app. Time duration in minutes of offline event.
 | ||||||
|  |     deleted?: boolean; // Calculated in the app. Whether it has been deleted in offline.
 | ||||||
|  |     ispast?: boolean; // Calculated in the app. Whether the event is in the past.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; | |||||||
| import { CoreLoggerProvider } from '@providers/logger'; | import { CoreLoggerProvider } from '@providers/logger'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreCourseProvider } from '@core/course/providers/course'; | import { CoreCourseProvider } from '@core/course/providers/course'; | ||||||
| import { AddonCalendarProvider } from './calendar'; | import { AddonCalendarProvider, AddonCalendarCalendarEvent } from './calendar'; | ||||||
| import { CoreConstants } from '@core/constants'; | import { CoreConstants } from '@core/constants'; | ||||||
| import { CoreConfigProvider } from '@providers/config'; | import { CoreConfigProvider } from '@providers/config'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| @ -130,11 +130,11 @@ export class AddonCalendarHelperProvider { | |||||||
|      * |      * | ||||||
|      * @param e Event to format. |      * @param e Event to format. | ||||||
|      */ |      */ | ||||||
|     formatEventData(e: any): void { |     formatEventData(e: AddonCalendarCalendarEvent): void { | ||||||
|         e.icon = this.EVENTICONS[e.eventtype] || false; |         e.eventIcon = this.EVENTICONS[e.eventtype] || ''; | ||||||
|         if (!e.icon) { |         if (!e.eventIcon) { | ||||||
|             e.icon = this.courseProvider.getModuleIconSrc(e.modulename); |             e.eventIcon = this.courseProvider.getModuleIconSrc(e.modulename); | ||||||
|             e.moduleIcon = e.icon; |             e.moduleIcon = e.eventIcon; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         e.formattedType = this.calendarProvider.getEventType(e); |         e.formattedType = this.calendarProvider.getEventType(e); | ||||||
| @ -160,7 +160,7 @@ export class AddonCalendarHelperProvider { | |||||||
|      * @param eventTypes Result of getAllowedEventTypes. |      * @param eventTypes Result of getAllowedEventTypes. | ||||||
|      * @return Options. |      * @return Options. | ||||||
|      */ |      */ | ||||||
|     getEventTypeOptions(eventTypes: any): {name: string, value: string}[] { |     getEventTypeOptions(eventTypes: {[name: string]: boolean}): {name: string, value: string}[] { | ||||||
|         const options = []; |         const options = []; | ||||||
| 
 | 
 | ||||||
|         if (eventTypes.user) { |         if (eventTypes.user) { | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Component, ViewChild, Input } from '@angular/core'; | |||||||
| import { Content, NavController } from 'ionic-angular'; | import { Content, NavController } from 'ionic-angular'; | ||||||
| import { CoreAppProvider } from '@providers/app'; | import { CoreAppProvider } from '@providers/app'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | import { AddonCompetencyProvider, AddonCompetencyDataForCourseCompetenciesPageResult } from '../../providers/competency'; | ||||||
| import { AddonCompetencyHelperProvider } from '../../providers/helper'; | import { AddonCompetencyHelperProvider } from '../../providers/helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -33,7 +33,7 @@ export class AddonCompetencyCourseComponent { | |||||||
|     @Input() userId: number; |     @Input() userId: number; | ||||||
| 
 | 
 | ||||||
|     competenciesLoaded = false; |     competenciesLoaded = false; | ||||||
|     competencies: any; |     competencies: AddonCompetencyDataForCourseCompetenciesPageResult; | ||||||
|     user: any; |     user: any; | ||||||
| 
 | 
 | ||||||
|     constructor(private navCtrl: NavController, private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, |     constructor(private navCtrl: NavController, private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, | ||||||
|  | |||||||
| @ -17,7 +17,10 @@ import { IonicPage, NavParams } from 'ionic-angular'; | |||||||
| import { TranslateService } from '@ngx-translate/core'; | import { TranslateService } from '@ngx-translate/core'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | import { | ||||||
|  |     AddonCompetencyProvider, AddonCompetencyDataForCourseCompetenciesPageResult, AddonCompetencyDataForPlanPageResult, | ||||||
|  |     AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForCourseCompetenciesPageCompetency | ||||||
|  | } from '../../providers/competency'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays the list of competencies of a learning plan. |  * Page that displays the list of competencies of a learning plan. | ||||||
| @ -36,7 +39,7 @@ export class AddonCompetencyCompetenciesPage { | |||||||
|     protected userId: number; |     protected userId: number; | ||||||
| 
 | 
 | ||||||
|     competenciesLoaded = false; |     competenciesLoaded = false; | ||||||
|     competencies = []; |     competencies: AddonCompetencyDataForPlanPageCompetency[] | AddonCompetencyDataForCourseCompetenciesPageCompetency[] = []; | ||||||
|     title: string; |     title: string; | ||||||
| 
 | 
 | ||||||
|     constructor(navParams: NavParams, private translate: TranslateService, private domUtils: CoreDomUtilsProvider, |     constructor(navParams: NavParams, private translate: TranslateService, private domUtils: CoreDomUtilsProvider, | ||||||
| @ -59,7 +62,7 @@ export class AddonCompetencyCompetenciesPage { | |||||||
|         this.fetchCompetencies().then(() => { |         this.fetchCompetencies().then(() => { | ||||||
|             if (!this.competencyId && this.splitviewCtrl.isOn() && this.competencies.length > 0) { |             if (!this.competencyId && this.splitviewCtrl.isOn() && this.competencies.length > 0) { | ||||||
|                 // Take first and load it.
 |                 // Take first and load it.
 | ||||||
|                 this.openCompetency(this.competencies[0].id); |                 this.openCompetency(this.competencies[0].competency.id); | ||||||
|             } |             } | ||||||
|         }).finally(() => { |         }).finally(() => { | ||||||
|             this.competenciesLoaded = true; |             this.competenciesLoaded = true; | ||||||
| @ -72,7 +75,7 @@ export class AddonCompetencyCompetenciesPage { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected fetchCompetencies(): Promise<void> { |     protected fetchCompetencies(): Promise<void> { | ||||||
|         let promise; |         let promise: Promise<AddonCompetencyDataForPlanPageResult | AddonCompetencyDataForCourseCompetenciesPageResult>; | ||||||
| 
 | 
 | ||||||
|         if (this.planId) { |         if (this.planId) { | ||||||
|             promise = this.competencyProvider.getLearningPlan(this.planId); |             promise = this.competencyProvider.getLearningPlan(this.planId); | ||||||
| @ -83,13 +86,16 @@ export class AddonCompetencyCompetenciesPage { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return promise.then((response) => { |         return promise.then((response) => { | ||||||
|             if (response.competencycount <= 0) { |  | ||||||
|                 return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound')); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             if (this.planId) { |             if (this.planId) { | ||||||
|                 this.title = response.plan.name; |                 const resp = <AddonCompetencyDataForPlanPageResult> response; | ||||||
|                 this.userId = response.plan.userid; | 
 | ||||||
|  |                 if (resp.competencycount <= 0) { | ||||||
|  |                     return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound')); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 this.title = resp.plan.name; | ||||||
|  |                 this.userId = resp.plan.userid; | ||||||
|             } else { |             } else { | ||||||
|                 this.title = this.translate.instant('addon.competency.coursecompetencies'); |                 this.title = this.translate.instant('addon.competency.coursecompetencies'); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -51,22 +51,22 @@ | |||||||
|                     <core-format-text [text]="activity.name"></core-format-text> |                     <core-format-text [text]="activity.name"></core-format-text> | ||||||
|                 </a> |                 </a> | ||||||
|             </ion-item> |             </ion-item> | ||||||
|             <ion-item text-wrap *ngIf="competency.usercompetency.status"> |             <ion-item text-wrap *ngIf="userCompetency.status"> | ||||||
|                 <strong>{{ 'addon.competency.reviewstatus' | translate }}</strong> |                 <strong>{{ 'addon.competency.reviewstatus' | translate }}</strong> | ||||||
|                 {{ competency.usercompetency.statusname }} |                 {{ userCompetency.statusname }} | ||||||
|             </ion-item> |             </ion-item> | ||||||
|             <ion-item text-wrap> |             <ion-item text-wrap> | ||||||
|                 <strong>{{ 'addon.competency.proficient' | translate }}</strong> |                 <strong>{{ 'addon.competency.proficient' | translate }}</strong> | ||||||
|                 <ion-badge color="success" *ngIf="competency.usercompetency.proficiency"> |                 <ion-badge color="success" *ngIf="userCompetency.proficiency"> | ||||||
|                     {{ 'core.yes' | translate }} |                     {{ 'core.yes' | translate }} | ||||||
|                 </ion-badge> |                 </ion-badge> | ||||||
|                 <ion-badge color="danger" *ngIf="!competency.usercompetency.proficiency"> |                 <ion-badge color="danger" *ngIf="!userCompetency.proficiency"> | ||||||
|                     {{ 'core.no' | translate }} |                     {{ 'core.no' | translate }} | ||||||
|                 </ion-badge> |                 </ion-badge> | ||||||
|             </ion-item> |             </ion-item> | ||||||
|             <ion-item text-wrap> |             <ion-item text-wrap> | ||||||
|                 <strong>{{ 'addon.competency.rating' | translate }}</strong> |                 <strong>{{ 'addon.competency.rating' | translate }}</strong> | ||||||
|                 <ion-badge color="dark">{{ competency.usercompetency.gradename }}</ion-badge> |                 <ion-badge color="dark">{{ userCompetency.gradename }}</ion-badge> | ||||||
|             </ion-item> |             </ion-item> | ||||||
|         </ion-card> |         </ion-card> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -18,8 +18,14 @@ import { TranslateService } from '@ngx-translate/core'; | |||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | import { | ||||||
|  |     AddonCompetencyProvider, AddonCompetencyUserCompetencySummary, AddonCompetencyUserCompetencySummaryInPlan, | ||||||
|  |     AddonCompetencyUserCompetencySummaryInCourse, AddonCompetencyUserCompetencyPlan, | ||||||
|  |     AddonCompetencyUserCompetency, AddonCompetencyUserCompetencyCourse | ||||||
|  | } from '../../providers/competency'; | ||||||
| import { AddonCompetencyHelperProvider } from '../../providers/helper'; | import { AddonCompetencyHelperProvider } from '../../providers/helper'; | ||||||
|  | import { CoreUserSummary } from '@core/user/providers/user'; | ||||||
|  | import { CoreCourseModuleSummary } from '@core/course/providers/course'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays a learning plan. |  * Page that displays a learning plan. | ||||||
| @ -36,9 +42,10 @@ export class AddonCompetencyCompetencyPage { | |||||||
|     courseId: number; |     courseId: number; | ||||||
|     userId: number; |     userId: number; | ||||||
|     planStatus: number; |     planStatus: number; | ||||||
|     coursemodules: any; |     coursemodules: CoreCourseModuleSummary[]; | ||||||
|     user: any; |     user: CoreUserSummary; | ||||||
|     competency: any; |     competency: AddonCompetencyUserCompetencySummary; | ||||||
|  |     userCompetency: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse; | ||||||
| 
 | 
 | ||||||
|     constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, |     constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, | ||||||
|             private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, |             private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, | ||||||
| @ -79,7 +86,8 @@ export class AddonCompetencyCompetencyPage { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected fetchCompetency(): Promise<void> { |     protected fetchCompetency(): Promise<void> { | ||||||
|         let promise; |         let promise: Promise<AddonCompetencyUserCompetencySummaryInPlan | AddonCompetencyUserCompetencySummaryInCourse>; | ||||||
|  | 
 | ||||||
|         if (this.planId) { |         if (this.planId) { | ||||||
|             this.planStatus = null; |             this.planStatus = null; | ||||||
|             promise = this.competencyProvider.getCompetencyInPlan(this.planId, this.competencyId); |             promise = this.competencyProvider.getCompetencyInPlan(this.planId, this.competencyId); | ||||||
| @ -90,23 +98,21 @@ export class AddonCompetencyCompetencyPage { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return promise.then((competency) => { |         return promise.then((competency) => { | ||||||
|             competency.usercompetencysummary.usercompetency = competency.usercompetencysummary.usercompetencyplan || | 
 | ||||||
|                 competency.usercompetencysummary.usercompetency; |  | ||||||
|             this.competency = competency.usercompetencysummary; |             this.competency = competency.usercompetencysummary; | ||||||
|  |             this.userCompetency = this.competency.usercompetencyplan || this.competency.usercompetency; | ||||||
| 
 | 
 | ||||||
|             if (this.planId) { |             if (this.planId) { | ||||||
|                 this.planStatus = competency.plan.status; |                 this.planStatus = (<AddonCompetencyUserCompetencySummaryInPlan> competency).plan.status; | ||||||
|                 this.competency.usercompetency.statusname = |                 this.competency.usercompetency.statusname = | ||||||
|                     this.competencyHelperProvider.getCompetencyStatusName(this.competency.usercompetency.status); |                     this.competencyHelperProvider.getCompetencyStatusName(this.competency.usercompetency.status); | ||||||
|             } else { |             } else { | ||||||
|                 this.competency.usercompetency = this.competency.usercompetencycourse; |                 this.userCompetency = this.competency.usercompetencycourse; | ||||||
|                 this.coursemodules = competency.coursemodules; |                 this.coursemodules = (<AddonCompetencyUserCompetencySummaryInCourse> competency).coursemodules; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (this.competency.user.id != this.sitesProvider.getCurrentSiteUserId()) { |             if (this.competency.user.id != this.sitesProvider.getCurrentSiteUserId()) { | ||||||
|                 this.competency.user.profileimageurl = this.competency.user.profileimageurl || true; |                 // Get the user profile from the returned object.
 | ||||||
| 
 |  | ||||||
|                 // Get the user profile image from the returned object.
 |  | ||||||
|                 this.user = this.competency.user; |                 this.user = this.competency.user; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Component, Optional } from '@angular/core'; | |||||||
| import { IonicPage, NavController, NavParams } from 'ionic-angular'; | import { IonicPage, NavController, NavParams } from 'ionic-angular'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | import { AddonCompetencyProvider, AddonCompetencySummary } from '../../providers/competency'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays a learning plan. |  * Page that displays a learning plan. | ||||||
| @ -29,7 +29,7 @@ import { AddonCompetencyProvider } from '../../providers/competency'; | |||||||
| export class AddonCompetencyCompetencySummaryPage { | export class AddonCompetencyCompetencySummaryPage { | ||||||
|     competencyLoaded = false; |     competencyLoaded = false; | ||||||
|     competencyId: number; |     competencyId: number; | ||||||
|     competency: any; |     competency: AddonCompetencySummary; | ||||||
| 
 | 
 | ||||||
|     constructor(private navCtrl: NavController, navParams: NavParams, private domUtils: CoreDomUtilsProvider, |     constructor(private navCtrl: NavController, navParams: NavParams, private domUtils: CoreDomUtilsProvider, | ||||||
|             @Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider) { |             @Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider) { | ||||||
| @ -41,8 +41,7 @@ export class AddonCompetencyCompetencySummaryPage { | |||||||
|      */ |      */ | ||||||
|     ionViewDidLoad(): void { |     ionViewDidLoad(): void { | ||||||
|         this.fetchCompetency().then(() => { |         this.fetchCompetency().then(() => { | ||||||
|             const name = this.competency.competency && this.competency.competency.competency && |             const name = this.competency.competency && this.competency.competency.shortname; | ||||||
|                     this.competency.competency.competency.shortname; |  | ||||||
| 
 | 
 | ||||||
|             this.competencyProvider.logCompetencyView(this.competencyId, name).catch(() => { |             this.competencyProvider.logCompetencyView(this.competencyId, name).catch(() => { | ||||||
|                 // Ignore errors.
 |                 // Ignore errors.
 | ||||||
|  | |||||||
| @ -46,7 +46,8 @@ | |||||||
|                 </ion-item> |                 </ion-item> | ||||||
|                 <a ion-item text-wrap *ngFor="let competency of plan.competencies" (click)="openCompetency(competency.competency.id)" [title]="competency.competency.shortname"> |                 <a ion-item text-wrap *ngFor="let competency of plan.competencies" (click)="openCompetency(competency.competency.id)" [title]="competency.competency.shortname"> | ||||||
|                     <h2>{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></h2> |                     <h2>{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></h2> | ||||||
|                     <ion-badge item-end [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge> |                     <ion-badge *ngIf="competency.usercompetencyplan" item-end [color]="competency.usercompetencyplan.proficiency ? 'success' : 'danger'">{{ competency.usercompetencyplan.gradename }}</ion-badge> | ||||||
|  |                     <ion-badge *ngIf="!competency.usercompetencyplan" item-end [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge> | ||||||
|                 </a> |                 </a> | ||||||
|             </ion-list> |             </ion-list> | ||||||
|         </ion-card> |         </ion-card> | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ import { IonicPage, NavController, NavParams } from 'ionic-angular'; | |||||||
| import { CoreAppProvider } from '@providers/app'; | import { CoreAppProvider } from '@providers/app'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | import { AddonCompetencyProvider, AddonCompetencyDataForPlanPageResult } from '../../providers/competency'; | ||||||
| import { AddonCompetencyHelperProvider } from '../../providers/helper'; | import { AddonCompetencyHelperProvider } from '../../providers/helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -31,7 +31,7 @@ import { AddonCompetencyHelperProvider } from '../../providers/helper'; | |||||||
| export class AddonCompetencyPlanPage { | export class AddonCompetencyPlanPage { | ||||||
|     protected planId: number; |     protected planId: number; | ||||||
|     planLoaded = false; |     planLoaded = false; | ||||||
|     plan: any; |     plan: AddonCompetencyDataForPlanPageResult; | ||||||
|     user: any; |     user: any; | ||||||
| 
 | 
 | ||||||
|     constructor(private navCtrl: NavController, navParams: NavParams, private appProvider: CoreAppProvider, |     constructor(private navCtrl: NavController, navParams: NavParams, private appProvider: CoreAppProvider, | ||||||
| @ -62,9 +62,6 @@ export class AddonCompetencyPlanPage { | |||||||
|                 this.user = user; |                 this.user = user; | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             plan.competencies.forEach((competency) => { |  | ||||||
|                 competency.usercompetency = competency.usercompetencyplan || competency.usercompetency; |  | ||||||
|             }); |  | ||||||
|             this.plan = plan; |             this.plan = plan; | ||||||
|         }).catch((message) => { |         }).catch((message) => { | ||||||
|             this.domUtils.showErrorModalDefault(message, 'Error getting learning plan data.'); |             this.domUtils.showErrorModalDefault(message, 'Error getting learning plan data.'); | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Component, ViewChild } from '@angular/core'; | |||||||
| import { IonicPage, NavParams } from 'ionic-angular'; | import { IonicPage, NavParams } from 'ionic-angular'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | import { AddonCompetencyProvider, AddonCompetencyPlan } from '../../providers/competency'; | ||||||
| import { AddonCompetencyHelperProvider } from '../../providers/helper'; | import { AddonCompetencyHelperProvider } from '../../providers/helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -33,7 +33,7 @@ export class AddonCompetencyPlanListPage { | |||||||
|     protected userId: number; |     protected userId: number; | ||||||
|     protected planId: number; |     protected planId: number; | ||||||
|     plansLoaded = false; |     plansLoaded = false; | ||||||
|     plans = []; |     plans: AddonCompetencyPlan[] = []; | ||||||
| 
 | 
 | ||||||
|     constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private competencyProvider: AddonCompetencyProvider, |     constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private competencyProvider: AddonCompetencyProvider, | ||||||
|             private competencyHelperProvider: AddonCompetencyHelperProvider) { |             private competencyHelperProvider: AddonCompetencyHelperProvider) { | ||||||
| @ -66,7 +66,7 @@ export class AddonCompetencyPlanListPage { | |||||||
|      */ |      */ | ||||||
|     protected fetchLearningPlans(): Promise<void> { |     protected fetchLearningPlans(): Promise<void> { | ||||||
|         return this.competencyProvider.getLearningPlans(this.userId).then((plans) => { |         return this.competencyProvider.getLearningPlans(this.userId).then((plans) => { | ||||||
|             plans.forEach((plan) => { |             plans.forEach((plan: AddonCompetencyPlanFormatted) => { | ||||||
|                 plan.statusname = this.competencyHelperProvider.getPlanStatusName(plan.status); |                 plan.statusname = this.competencyHelperProvider.getPlanStatusName(plan.status); | ||||||
|                 switch (plan.status) { |                 switch (plan.status) { | ||||||
|                     case AddonCompetencyProvider.STATUS_ACTIVE: |                     case AddonCompetencyProvider.STATUS_ACTIVE: | ||||||
| @ -109,3 +109,10 @@ export class AddonCompetencyPlanListPage { | |||||||
|         this.splitviewCtrl.push('AddonCompetencyPlanPage', { planId }); |         this.splitviewCtrl.push('AddonCompetencyPlanPage', { planId }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Competency plan with some calculated data. | ||||||
|  |  */ | ||||||
|  | type AddonCompetencyPlanFormatted = AddonCompetencyPlan & { | ||||||
|  |     statuscolor?: string; // Calculated in the app. Color of the plan's status.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -17,6 +17,9 @@ import { CoreLoggerProvider } from '@providers/logger'; | |||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
|  | import { CoreCommentsArea } from '@core/comments/providers/comments'; | ||||||
|  | import { CoreUserSummary } from '@core/user/providers/user'; | ||||||
|  | import { CoreCourseSummary, CoreCourseModuleSummary } from '@core/course/providers/course'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to handle caompetency learning plans. |  * Service to handle caompetency learning plans. | ||||||
| @ -147,7 +150,7 @@ export class AddonCompetencyProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise to be resolved when the plans are retrieved. |      * @return Promise to be resolved when the plans are retrieved. | ||||||
|      */ |      */ | ||||||
|     getLearningPlans(userId?: number, siteId?: string): Promise<any> { |     getLearningPlans(userId?: number, siteId?: string): Promise<AddonCompetencyPlan[]> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| 
 | 
 | ||||||
| @ -161,7 +164,9 @@ export class AddonCompetencyProvider { | |||||||
|                     updateFrequency: CoreSite.FREQUENCY_RARELY |                     updateFrequency: CoreSite.FREQUENCY_RARELY | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('tool_lp_data_for_plans_page', params, preSets).then((response) => { |             return site.read('tool_lp_data_for_plans_page', params, preSets) | ||||||
|  |                     .then((response: AddonCompetencyDataForPlansPageResult): any => { | ||||||
|  | 
 | ||||||
|                 if (response.plans) { |                 if (response.plans) { | ||||||
|                     return response.plans; |                     return response.plans; | ||||||
|                 } |                 } | ||||||
| @ -176,9 +181,9 @@ export class AddonCompetencyProvider { | |||||||
|      * |      * | ||||||
|      * @param planId ID of the plan. |      * @param planId ID of the plan. | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise to be resolved when the plans are retrieved. |      * @return Promise to be resolved when the plan is retrieved. | ||||||
|      */ |      */ | ||||||
|     getLearningPlan(planId: number, siteId?: string): Promise<any> { |     getLearningPlan(planId: number, siteId?: string): Promise<AddonCompetencyDataForPlanPageResult> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| 
 | 
 | ||||||
|             this.logger.debug('Get plan ' + planId); |             this.logger.debug('Get plan ' + planId); | ||||||
| @ -191,7 +196,9 @@ export class AddonCompetencyProvider { | |||||||
|                     updateFrequency: CoreSite.FREQUENCY_RARELY |                     updateFrequency: CoreSite.FREQUENCY_RARELY | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('tool_lp_data_for_plan_page', params, preSets).then((response) => { |             return site.read('tool_lp_data_for_plan_page', params, preSets) | ||||||
|  |                     .then((response: AddonCompetencyDataForPlanPageResult): any => { | ||||||
|  | 
 | ||||||
|                 if (response.plan) { |                 if (response.plan) { | ||||||
|                     return response; |                     return response; | ||||||
|                 } |                 } | ||||||
| @ -207,9 +214,11 @@ export class AddonCompetencyProvider { | |||||||
|      * @param planId ID of the plan. |      * @param planId ID of the plan. | ||||||
|      * @param competencyId ID of the competency. |      * @param competencyId ID of the competency. | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise to be resolved when the plans are retrieved. |      * @return Promise to be resolved when the competency is retrieved. | ||||||
|      */ |      */ | ||||||
|     getCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise<any> { |     getCompetencyInPlan(planId: number, competencyId: number, siteId?: string) | ||||||
|  |             : Promise<AddonCompetencyUserCompetencySummaryInPlan> { | ||||||
|  | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| 
 | 
 | ||||||
|             this.logger.debug('Get competency ' + competencyId + ' in plan ' + planId); |             this.logger.debug('Get competency ' + competencyId + ' in plan ' + planId); | ||||||
| @ -223,7 +232,9 @@ export class AddonCompetencyProvider { | |||||||
|                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES |                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets).then((response) => { |             return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets) | ||||||
|  |                     .then((response: AddonCompetencyUserCompetencySummaryInPlan): any => { | ||||||
|  | 
 | ||||||
|                 if (response.usercompetencysummary) { |                 if (response.usercompetencysummary) { | ||||||
|                     return response; |                     return response; | ||||||
|                 } |                 } | ||||||
| @ -241,10 +252,10 @@ export class AddonCompetencyProvider { | |||||||
|      * @param userId ID of the user. If not defined, current user. |      * @param userId ID of the user. If not defined, current user. | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). |      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). | ||||||
|      * @return Promise to be resolved when the plans are retrieved. |      * @return Promise to be resolved when the competency is retrieved. | ||||||
|      */ |      */ | ||||||
|     getCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean) |     getCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean) | ||||||
|             : Promise<any> { |             : Promise<AddonCompetencyUserCompetencySummaryInCourse> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| @ -266,7 +277,9 @@ export class AddonCompetencyProvider { | |||||||
|                 preSets.emergencyCache = false; |                 preSets.emergencyCache = false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets).then((response) => { |             return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets) | ||||||
|  |                     .then((response: AddonCompetencyUserCompetencySummaryInCourse): any => { | ||||||
|  | 
 | ||||||
|                 if (response.usercompetencysummary) { |                 if (response.usercompetencysummary) { | ||||||
|                     return response; |                     return response; | ||||||
|                 } |                 } | ||||||
| @ -283,9 +296,11 @@ export class AddonCompetencyProvider { | |||||||
|      * @param userId ID of the user. If not defined, current user. |      * @param userId ID of the user. If not defined, current user. | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). |      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). | ||||||
|      * @return Promise to be resolved when the plans are retrieved. |      * @return Promise to be resolved when the competency summary is retrieved. | ||||||
|      */ |      */ | ||||||
|     getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any> { |     getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean) | ||||||
|  |             : Promise<AddonCompetencySummary> { | ||||||
|  | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| 
 | 
 | ||||||
| @ -305,7 +320,9 @@ export class AddonCompetencyProvider { | |||||||
|                 preSets.emergencyCache = false; |                 preSets.emergencyCache = false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('tool_lp_data_for_user_competency_summary', params, preSets).then((response) => { |             return site.read('tool_lp_data_for_user_competency_summary', params, preSets) | ||||||
|  |                     .then((response: AddonCompetencyUserCompetencySummary): any => { | ||||||
|  | 
 | ||||||
|                 if (response.competency) { |                 if (response.competency) { | ||||||
|                     return response.competency; |                     return response.competency; | ||||||
|                 } |                 } | ||||||
| @ -324,7 +341,9 @@ export class AddonCompetencyProvider { | |||||||
|      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). |      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). | ||||||
|      * @return Promise to be resolved when the course competencies are retrieved. |      * @return Promise to be resolved when the course competencies are retrieved. | ||||||
|      */ |      */ | ||||||
|     getCourseCompetencies(courseId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any> { |     getCourseCompetencies(courseId: number, userId?: number, siteId?: string, ignoreCache?: boolean) | ||||||
|  |             : Promise<AddonCompetencyDataForCourseCompetenciesPageResult> { | ||||||
|  | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| 
 | 
 | ||||||
|             this.logger.debug('Get course competencies for course ' + courseId); |             this.logger.debug('Get course competencies for course ' + courseId); | ||||||
| @ -342,7 +361,9 @@ export class AddonCompetencyProvider { | |||||||
|                 preSets.emergencyCache = false; |                 preSets.emergencyCache = false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('tool_lp_data_for_course_competencies_page', params, preSets).then((response) => { |             return site.read('tool_lp_data_for_course_competencies_page', params, preSets) | ||||||
|  |                     .then((response: AddonCompetencyDataForCourseCompetenciesPageResult): any => { | ||||||
|  | 
 | ||||||
|                 if (response.competencies) { |                 if (response.competencies) { | ||||||
|                     return response; |                     return response; | ||||||
|                 } |                 } | ||||||
| @ -356,11 +377,13 @@ export class AddonCompetencyProvider { | |||||||
|                 return response; |                 return response; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const promises = response.competencies.map((competency) => |             let promises: Promise<AddonCompetencyUserCompetencySummaryInCourse>[]; | ||||||
|  | 
 | ||||||
|  |             promises = response.competencies.map((competency) => | ||||||
|                 this.getCompetencyInCourse(courseId, competency.competency.id, userId, siteId) |                 this.getCompetencyInCourse(courseId, competency.competency.id, userId, siteId) | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|             return Promise.all(promises).then((responses: any[]) => { |             return Promise.all(promises).then((responses: AddonCompetencyUserCompetencySummaryInCourse[]) => { | ||||||
|                 responses.forEach((resp, index) => { |                 responses.forEach((resp, index) => { | ||||||
|                     response.competencies[index].usercompetencycourse = resp.usercompetencysummary.usercompetencycourse; |                     response.competencies[index].usercompetencycourse = resp.usercompetencysummary.usercompetencycourse; | ||||||
|                 }); |                 }); | ||||||
| @ -486,7 +509,7 @@ export class AddonCompetencyProvider { | |||||||
|      * @return Promise resolved when the WS call is successful. |      * @return Promise resolved when the WS call is successful. | ||||||
|      */ |      */ | ||||||
|     logCompetencyInPlanView(planId: number, competencyId: number, planStatus: number, name?: string, userId?: number, |     logCompetencyInPlanView(planId: number, competencyId: number, planStatus: number, name?: string, userId?: number, | ||||||
|             siteId?: string): Promise<any> { |             siteId?: string): Promise<void> { | ||||||
|         if (planId && competencyId) { |         if (planId && competencyId) { | ||||||
| 
 | 
 | ||||||
|             return this.sitesProvider.getSite(siteId).then((site) => { |             return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| @ -509,7 +532,11 @@ export class AddonCompetencyProvider { | |||||||
|                     userid: userId |                     userid: userId | ||||||
|                 }, siteId); |                 }, siteId); | ||||||
| 
 | 
 | ||||||
|                 return site.write(wsName, params, preSets); |                 return site.write(wsName, params, preSets).then((success: boolean) => { | ||||||
|  |                     if (!success) { | ||||||
|  |                         return Promise.reject(null); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -527,7 +554,7 @@ export class AddonCompetencyProvider { | |||||||
|      * @return Promise resolved when the WS call is successful. |      * @return Promise resolved when the WS call is successful. | ||||||
|      */ |      */ | ||||||
|     logCompetencyInCourseView(courseId: number, competencyId: number, name?: string, userId?: number, siteId?: string) |     logCompetencyInCourseView(courseId: number, competencyId: number, name?: string, userId?: number, siteId?: string) | ||||||
|             : Promise<any> { |             : Promise<void> { | ||||||
| 
 | 
 | ||||||
|         if (courseId && competencyId) { |         if (courseId && competencyId) { | ||||||
|             return this.sitesProvider.getSite(siteId).then((site) => { |             return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| @ -548,7 +575,11 @@ export class AddonCompetencyProvider { | |||||||
|                     userid: userId |                     userid: userId | ||||||
|                 }, siteId); |                 }, siteId); | ||||||
| 
 | 
 | ||||||
|                 return site.write(wsName, params, preSets); |                 return site.write(wsName, params, preSets).then((success: boolean) => { | ||||||
|  |                     if (!success) { | ||||||
|  |                         return Promise.reject(null); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -563,7 +594,7 @@ export class AddonCompetencyProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved when the WS call is successful. |      * @return Promise resolved when the WS call is successful. | ||||||
|      */ |      */ | ||||||
|     logCompetencyView(competencyId: number, name?: string, siteId?: string): Promise<any> { |     logCompetencyView(competencyId: number, name?: string, siteId?: string): Promise<void> { | ||||||
|         if (competencyId) { |         if (competencyId) { | ||||||
|             return this.sitesProvider.getSite(siteId).then((site) => { |             return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|                 const params = { |                 const params = { | ||||||
| @ -576,10 +607,401 @@ export class AddonCompetencyProvider { | |||||||
| 
 | 
 | ||||||
|                 this.pushNotificationsProvider.logViewEvent(competencyId, name, 'competency', wsName, {}, siteId); |                 this.pushNotificationsProvider.logViewEvent(competencyId, name, 'competency', wsName, {}, siteId); | ||||||
| 
 | 
 | ||||||
|                 return site.write('core_competency_competency_viewed', params, preSets); |                 return site.write(wsName, params, preSets).then((success: boolean) => { | ||||||
|  |                     if (!success) { | ||||||
|  |                         return Promise.reject(null); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return Promise.reject(null); |         return Promise.reject(null); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's plan_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyPlan = { | ||||||
|  |     name: string; // Name.
 | ||||||
|  |     description: string; // Description.
 | ||||||
|  |     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     userid: number; // Userid.
 | ||||||
|  |     templateid: number; // Templateid.
 | ||||||
|  |     origtemplateid: number; // Origtemplateid.
 | ||||||
|  |     status: number; // Status.
 | ||||||
|  |     duedate: number; // Duedate.
 | ||||||
|  |     reviewerid: number; // Reviewerid.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  |     statusname: string; // Statusname.
 | ||||||
|  |     isbasedontemplate: boolean; // Isbasedontemplate.
 | ||||||
|  |     canmanage: boolean; // Canmanage.
 | ||||||
|  |     canrequestreview: boolean; // Canrequestreview.
 | ||||||
|  |     canreview: boolean; // Canreview.
 | ||||||
|  |     canbeedited: boolean; // Canbeedited.
 | ||||||
|  |     isactive: boolean; // Isactive.
 | ||||||
|  |     isdraft: boolean; // Isdraft.
 | ||||||
|  |     iscompleted: boolean; // Iscompleted.
 | ||||||
|  |     isinreview: boolean; // Isinreview.
 | ||||||
|  |     iswaitingforreview: boolean; // Iswaitingforreview.
 | ||||||
|  |     isreopenallowed: boolean; // Isreopenallowed.
 | ||||||
|  |     iscompleteallowed: boolean; // Iscompleteallowed.
 | ||||||
|  |     isunlinkallowed: boolean; // Isunlinkallowed.
 | ||||||
|  |     isrequestreviewallowed: boolean; // Isrequestreviewallowed.
 | ||||||
|  |     iscancelreviewrequestallowed: boolean; // Iscancelreviewrequestallowed.
 | ||||||
|  |     isstartreviewallowed: boolean; // Isstartreviewallowed.
 | ||||||
|  |     isstopreviewallowed: boolean; // Isstopreviewallowed.
 | ||||||
|  |     isapproveallowed: boolean; // Isapproveallowed.
 | ||||||
|  |     isunapproveallowed: boolean; // Isunapproveallowed.
 | ||||||
|  |     duedateformatted: string; // Duedateformatted.
 | ||||||
|  |     commentarea: CoreCommentsArea; | ||||||
|  |     reviewer?: CoreUserSummary; | ||||||
|  |     template?: AddonCompetencyTemplate; | ||||||
|  |     url: string; // Url.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's template_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyTemplate = { | ||||||
|  |     shortname: string; // Shortname.
 | ||||||
|  |     description: string; // Description.
 | ||||||
|  |     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     duedate: number; // Duedate.
 | ||||||
|  |     visible: boolean; // Visible.
 | ||||||
|  |     contextid: number; // Contextid.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  |     duedateformatted: string; // Duedateformatted.
 | ||||||
|  |     cohortscount: number; // Cohortscount.
 | ||||||
|  |     planscount: number; // Planscount.
 | ||||||
|  |     canmanage: boolean; // Canmanage.
 | ||||||
|  |     canread: boolean; // Canread.
 | ||||||
|  |     contextname: string; // Contextname.
 | ||||||
|  |     contextnamenoprefix: string; // Contextnamenoprefix.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's competency_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyCompetency = { | ||||||
|  |     shortname: string; // Shortname.
 | ||||||
|  |     idnumber: string; // Idnumber.
 | ||||||
|  |     description: string; // Description.
 | ||||||
|  |     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     sortorder: number; // Sortorder.
 | ||||||
|  |     parentid: number; // Parentid.
 | ||||||
|  |     path: string; // Path.
 | ||||||
|  |     ruleoutcome: number; // Ruleoutcome.
 | ||||||
|  |     ruletype: string; // Ruletype.
 | ||||||
|  |     ruleconfig: string; // Ruleconfig.
 | ||||||
|  |     scaleid: number; // Scaleid.
 | ||||||
|  |     scaleconfiguration: string; // Scaleconfiguration.
 | ||||||
|  |     competencyframeworkid: number; // Competencyframeworkid.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's competency_path_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyPath = { | ||||||
|  |     ancestors: AddonCompetencyPathNode[]; // Ancestors.
 | ||||||
|  |     framework: AddonCompetencyPathNode; | ||||||
|  |     pluginbaseurl: string; // Pluginbaseurl.
 | ||||||
|  |     pagecontextid: number; // Pagecontextid.
 | ||||||
|  |     showlinks: boolean; // Showlinks.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's path_node_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyPathNode = { | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     name: string; // Name.
 | ||||||
|  |     first: boolean; // First.
 | ||||||
|  |     last: boolean; // Last.
 | ||||||
|  |     position: number; // Position.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's user_competency_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyUserCompetency = { | ||||||
|  |     userid: number; // Userid.
 | ||||||
|  |     competencyid: number; // Competencyid.
 | ||||||
|  |     status: number; // Status.
 | ||||||
|  |     reviewerid: number; // Reviewerid.
 | ||||||
|  |     proficiency: boolean; // Proficiency.
 | ||||||
|  |     grade: number; // Grade.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  |     canrequestreview: boolean; // Canrequestreview.
 | ||||||
|  |     canreview: boolean; // Canreview.
 | ||||||
|  |     gradename: string; // Gradename.
 | ||||||
|  |     isrequestreviewallowed: boolean; // Isrequestreviewallowed.
 | ||||||
|  |     iscancelreviewrequestallowed: boolean; // Iscancelreviewrequestallowed.
 | ||||||
|  |     isstartreviewallowed: boolean; // Isstartreviewallowed.
 | ||||||
|  |     isstopreviewallowed: boolean; // Isstopreviewallowed.
 | ||||||
|  |     isstatusidle: boolean; // Isstatusidle.
 | ||||||
|  |     isstatusinreview: boolean; // Isstatusinreview.
 | ||||||
|  |     isstatuswaitingforreview: boolean; // Isstatuswaitingforreview.
 | ||||||
|  |     proficiencyname: string; // Proficiencyname.
 | ||||||
|  |     reviewer?: CoreUserSummary; | ||||||
|  |     statusname: string; // Statusname.
 | ||||||
|  |     url: string; // Url.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's user_competency_plan_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyUserCompetencyPlan = { | ||||||
|  |     userid: number; // Userid.
 | ||||||
|  |     competencyid: number; // Competencyid.
 | ||||||
|  |     proficiency: boolean; // Proficiency.
 | ||||||
|  |     grade: number; // Grade.
 | ||||||
|  |     planid: number; // Planid.
 | ||||||
|  |     sortorder: number; // Sortorder.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  |     gradename: string; // Gradename.
 | ||||||
|  |     proficiencyname: string; // Proficiencyname.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's user_competency_summary_in_plan_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyUserCompetencySummaryInPlan = { | ||||||
|  |     usercompetencysummary: AddonCompetencyUserCompetencySummary; | ||||||
|  |     plan: AddonCompetencyPlan; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's user_competency_summary_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyUserCompetencySummary = { | ||||||
|  |     showrelatedcompetencies: boolean; // Showrelatedcompetencies.
 | ||||||
|  |     cangrade: boolean; // Cangrade.
 | ||||||
|  |     competency: AddonCompetencySummary; | ||||||
|  |     user: CoreUserSummary; | ||||||
|  |     usercompetency?: AddonCompetencyUserCompetency; | ||||||
|  |     usercompetencyplan?: AddonCompetencyUserCompetencyPlan; | ||||||
|  |     usercompetencycourse?: AddonCompetencyUserCompetencyCourse; | ||||||
|  |     evidence: AddonCompetencyEvidence[]; // Evidence.
 | ||||||
|  |     commentarea?: CoreCommentsArea; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's competency_summary_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencySummary = { | ||||||
|  |     linkedcourses: CoreCourseSummary; // Linkedcourses.
 | ||||||
|  |     relatedcompetencies: AddonCompetencyCompetency[]; // Relatedcompetencies.
 | ||||||
|  |     competency: AddonCompetencyCompetency; | ||||||
|  |     framework: AddonCompetencyFramework; | ||||||
|  |     hascourses: boolean; // Hascourses.
 | ||||||
|  |     hasrelatedcompetencies: boolean; // Hasrelatedcompetencies.
 | ||||||
|  |     scaleid: number; // Scaleid.
 | ||||||
|  |     scaleconfiguration: string; // Scaleconfiguration.
 | ||||||
|  |     taxonomyterm: string; // Taxonomyterm.
 | ||||||
|  |     comppath: AddonCompetencyPath; | ||||||
|  |     pluginbaseurl: string; // Pluginbaseurl.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's competency_framework_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyFramework = { | ||||||
|  |     shortname: string; // Shortname.
 | ||||||
|  |     idnumber: string; // Idnumber.
 | ||||||
|  |     description: string; // Description.
 | ||||||
|  |     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     visible: boolean; // Visible.
 | ||||||
|  |     scaleid: number; // Scaleid.
 | ||||||
|  |     scaleconfiguration: string; // Scaleconfiguration.
 | ||||||
|  |     contextid: number; // Contextid.
 | ||||||
|  |     taxonomies: string; // Taxonomies.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  |     canmanage: boolean; // Canmanage.
 | ||||||
|  |     competenciescount: number; // Competenciescount.
 | ||||||
|  |     contextname: string; // Contextname.
 | ||||||
|  |     contextnamenoprefix: string; // Contextnamenoprefix.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's user_competency_course_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyUserCompetencyCourse = { | ||||||
|  |     userid: number; // Userid.
 | ||||||
|  |     courseid: number; // Courseid.
 | ||||||
|  |     competencyid: number; // Competencyid.
 | ||||||
|  |     proficiency: boolean; // Proficiency.
 | ||||||
|  |     grade: number; // Grade.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  |     gradename: string; // Gradename.
 | ||||||
|  |     proficiencyname: string; // Proficiencyname.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's evidence_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyEvidence = { | ||||||
|  |     usercompetencyid: number; // Usercompetencyid.
 | ||||||
|  |     contextid: number; // Contextid.
 | ||||||
|  |     action: number; // Action.
 | ||||||
|  |     actionuserid: number; // Actionuserid.
 | ||||||
|  |     descidentifier: string; // Descidentifier.
 | ||||||
|  |     desccomponent: string; // Desccomponent.
 | ||||||
|  |     desca: string; // Desca.
 | ||||||
|  |     url: string; // Url.
 | ||||||
|  |     grade: number; // Grade.
 | ||||||
|  |     note: string; // Note.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  |     actionuser?: CoreUserSummary; | ||||||
|  |     description: string; // Description.
 | ||||||
|  |     gradename: string; // Gradename.
 | ||||||
|  |     userdate: string; // Userdate.
 | ||||||
|  |     candelete: boolean; // Candelete.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's user_competency_summary_in_course_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyUserCompetencySummaryInCourse = { | ||||||
|  |     usercompetencysummary: AddonCompetencyUserCompetencySummary; | ||||||
|  |     course: CoreCourseSummary; | ||||||
|  |     coursemodules: CoreCourseModuleSummary[]; // Coursemodules.
 | ||||||
|  |     plans: AddonCompetencyPlan[]; // Plans.
 | ||||||
|  |     pluginbaseurl: string; // Pluginbaseurl.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's course_competency_settings_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyCourseCompetencySettings = { | ||||||
|  |     courseid: number; // Courseid.
 | ||||||
|  |     pushratingstouserplans: boolean; // Pushratingstouserplans.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's course_competency_statistics_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyCourseCompetencyStatistics = { | ||||||
|  |     competencycount: number; // Competencycount.
 | ||||||
|  |     proficientcompetencycount: number; // Proficientcompetencycount.
 | ||||||
|  |     proficientcompetencypercentage: number; // Proficientcompetencypercentage.
 | ||||||
|  |     proficientcompetencypercentageformatted: string; // Proficientcompetencypercentageformatted.
 | ||||||
|  |     leastproficient: AddonCompetencyCompetency[]; // Leastproficient.
 | ||||||
|  |     leastproficientcount: number; // Leastproficientcount.
 | ||||||
|  |     canbegradedincourse: boolean; // Canbegradedincourse.
 | ||||||
|  |     canmanagecoursecompetencies: boolean; // Canmanagecoursecompetencies.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by competency's course_competency_exporter. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyCourseCompetency = { | ||||||
|  |     courseid: number; // Courseid.
 | ||||||
|  |     competencyid: number; // Competencyid.
 | ||||||
|  |     sortorder: number; // Sortorder.
 | ||||||
|  |     ruleoutcome: number; // Ruleoutcome.
 | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     timecreated: number; // Timecreated.
 | ||||||
|  |     timemodified: number; // Timemodified.
 | ||||||
|  |     usermodified: number; // Usermodified.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS tool_lp_data_for_plans_page. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyDataForPlansPageResult = { | ||||||
|  |     userid: number; // The learning plan user id.
 | ||||||
|  |     plans: AddonCompetencyPlan[]; | ||||||
|  |     pluginbaseurl: string; // Url to the tool_lp plugin folder on this Moodle site.
 | ||||||
|  |     navigation: string[]; | ||||||
|  |     canreaduserevidence: boolean; // Can the current user view the user's evidence.
 | ||||||
|  |     canmanageuserplans: boolean; // Can the current user manage the user's plans.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS tool_lp_data_for_plan_page. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyDataForPlanPageResult = { | ||||||
|  |     plan: AddonCompetencyPlan; | ||||||
|  |     contextid: number; // Context ID.
 | ||||||
|  |     pluginbaseurl: string; // Plugin base URL.
 | ||||||
|  |     competencies: AddonCompetencyDataForPlanPageCompetency[]; | ||||||
|  |     competencycount: number; // Count of competencies.
 | ||||||
|  |     proficientcompetencycount: number; // Count of proficientcompetencies.
 | ||||||
|  |     proficientcompetencypercentage: number; // Percentage of competencies proficient.
 | ||||||
|  |     proficientcompetencypercentageformatted: string; // Displayable percentage.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Competency data returned by tool_lp_data_for_plan_page. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyDataForPlanPageCompetency = { | ||||||
|  |     competency: AddonCompetencyCompetency; | ||||||
|  |     comppath: AddonCompetencyPath; | ||||||
|  |     usercompetency?: AddonCompetencyUserCompetency; | ||||||
|  |     usercompetencyplan?: AddonCompetencyUserCompetencyPlan; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS tool_lp_data_for_course_competencies_page. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyDataForCourseCompetenciesPageResult = { | ||||||
|  |     courseid: number; // The current course id.
 | ||||||
|  |     pagecontextid: number; // The current page context ID.
 | ||||||
|  |     gradableuserid?: number; // Current user id, if the user is a gradable user.
 | ||||||
|  |     canmanagecompetencyframeworks: boolean; // User can manage competency frameworks.
 | ||||||
|  |     canmanagecoursecompetencies: boolean; // User can manage linked course competencies.
 | ||||||
|  |     canconfigurecoursecompetencies: boolean; // User can configure course competency settings.
 | ||||||
|  |     cangradecompetencies: boolean; // User can grade competencies.
 | ||||||
|  |     settings: AddonCompetencyCourseCompetencySettings; | ||||||
|  |     statistics: AddonCompetencyCourseCompetencyStatistics; | ||||||
|  |     competencies: AddonCompetencyDataForCourseCompetenciesPageCompetency[]; | ||||||
|  |     manageurl: string; // Url to the manage competencies page.
 | ||||||
|  |     pluginbaseurl: string; // Url to the course competencies page.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Competency data returned by tool_lp_data_for_course_competencies_page. | ||||||
|  |  */ | ||||||
|  | export type AddonCompetencyDataForCourseCompetenciesPageCompetency = { | ||||||
|  |     competency: AddonCompetencyCompetency; | ||||||
|  |     coursecompetency: AddonCompetencyCourseCompetency; | ||||||
|  |     coursemodules: CoreCourseModuleSummary[]; | ||||||
|  |     usercompetencycourse?: AddonCompetencyUserCompetencyCourse; | ||||||
|  |     ruleoutcomeoptions: { | ||||||
|  |         value: number; // The option value.
 | ||||||
|  |         text: string; // The name of the option.
 | ||||||
|  |         selected: boolean; // If this is the currently selected option.
 | ||||||
|  |     }[]; | ||||||
|  |     comppath: AddonCompetencyPath; | ||||||
|  |     plans: AddonCompetencyPlan[]; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ | |||||||
|         <ion-card *ngIf="completion && tracked"> |         <ion-card *ngIf="completion && tracked"> | ||||||
|             <ion-item text-wrap> |             <ion-item text-wrap> | ||||||
|                 <h2>{{ 'addon.coursecompletion.status' | translate }}</h2> |                 <h2>{{ 'addon.coursecompletion.status' | translate }}</h2> | ||||||
|                 <p>{{ completion.statusText | translate }}</p> |                 <p>{{ statusText | translate }}</p> | ||||||
|             </ion-item> |             </ion-item> | ||||||
|             <ion-item text-wrap> |             <ion-item text-wrap> | ||||||
|                 <h2>{{ 'addon.coursecompletion.required' | translate }}</h2> |                 <h2>{{ 'addon.coursecompletion.required' | translate }}</h2> | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ | |||||||
| import { Component, Input, OnInit } from '@angular/core'; | import { Component, Input, OnInit } from '@angular/core'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { AddonCourseCompletionProvider } from '../../providers/coursecompletion'; | import { AddonCourseCompletionProvider, AddonCourseCompletionCourseCompletionStatus } from '../../providers/coursecompletion'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Component that displays the course completion report. |  * Component that displays the course completion report. | ||||||
| @ -29,9 +29,10 @@ export class AddonCourseCompletionReportComponent implements OnInit { | |||||||
|     @Input() userId: number; |     @Input() userId: number; | ||||||
| 
 | 
 | ||||||
|     completionLoaded = false; |     completionLoaded = false; | ||||||
|     completion: any; |     completion: AddonCourseCompletionCourseCompletionStatus; | ||||||
|     showSelfComplete: boolean; |     showSelfComplete: boolean; | ||||||
|     tracked = true; // Whether completion is tracked.
 |     tracked = true; // Whether completion is tracked.
 | ||||||
|  |     statusText: string; | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         private sitesProvider: CoreSitesProvider, |         private sitesProvider: CoreSitesProvider, | ||||||
| @ -59,7 +60,7 @@ export class AddonCourseCompletionReportComponent implements OnInit { | |||||||
|     protected fetchCompletion(): Promise<any> { |     protected fetchCompletion(): Promise<any> { | ||||||
|         return this.courseCompletionProvider.getCompletion(this.courseId, this.userId).then((completion) => { |         return this.courseCompletionProvider.getCompletion(this.courseId, this.userId).then((completion) => { | ||||||
| 
 | 
 | ||||||
|             completion.statusText = this.courseCompletionProvider.getCompletedStatusText(completion); |             this.statusText = this.courseCompletionProvider.getCompletedStatusText(completion); | ||||||
| 
 | 
 | ||||||
|             this.completion = completion; |             this.completion = completion; | ||||||
|             this.showSelfComplete = this.courseCompletionProvider.canMarkSelfCompleted(this.userId, completion); |             this.showSelfComplete = this.courseCompletionProvider.canMarkSelfCompleted(this.userId, completion); | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; | |||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
|  | import { CoreWSExternalWarning } from '@providers/ws'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to handle course completion. |  * Service to handle course completion. | ||||||
| @ -43,7 +44,7 @@ export class AddonCourseCompletionProvider { | |||||||
|      * @param completion Course completion. |      * @param completion Course completion. | ||||||
|      * @return True if user can mark course as self completed, false otherwise. |      * @return True if user can mark course as self completed, false otherwise. | ||||||
|      */ |      */ | ||||||
|     canMarkSelfCompleted(userId: number, completion: any): boolean { |     canMarkSelfCompleted(userId: number, completion: AddonCourseCompletionCourseCompletionStatus): boolean { | ||||||
|         let selfCompletionActive = false, |         let selfCompletionActive = false, | ||||||
|             alreadyMarked = false; |             alreadyMarked = false; | ||||||
| 
 | 
 | ||||||
| @ -68,7 +69,7 @@ export class AddonCourseCompletionProvider { | |||||||
|      * @param completion Course completion. |      * @param completion Course completion. | ||||||
|      * @return Language code of the text to show. |      * @return Language code of the text to show. | ||||||
|      */ |      */ | ||||||
|     getCompletedStatusText(completion: any): string { |     getCompletedStatusText(completion: AddonCourseCompletionCourseCompletionStatus): string { | ||||||
|         if (completion.completed) { |         if (completion.completed) { | ||||||
|             return 'addon.coursecompletion.completed'; |             return 'addon.coursecompletion.completed'; | ||||||
|         } else { |         } else { | ||||||
| @ -96,7 +97,9 @@ export class AddonCourseCompletionProvider { | |||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Promise to be resolved when the completion is retrieved. |      * @return Promise to be resolved when the completion is retrieved. | ||||||
|      */ |      */ | ||||||
|     getCompletion(courseId: number, userId?: number, preSets?: any, siteId?: string): Promise<any> { |     getCompletion(courseId: number, userId?: number, preSets?: any, siteId?: string) | ||||||
|  |             : Promise<AddonCourseCompletionCourseCompletionStatus> { | ||||||
|  | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
|             preSets = preSets || {}; |             preSets = preSets || {}; | ||||||
| @ -112,7 +115,9 @@ export class AddonCourseCompletionProvider { | |||||||
|             preSets.updateFrequency = preSets.updateFrequency || CoreSite.FREQUENCY_SOMETIMES; |             preSets.updateFrequency = preSets.updateFrequency || CoreSite.FREQUENCY_SOMETIMES; | ||||||
|             preSets.cacheErrors = ['notenroled']; |             preSets.cacheErrors = ['notenroled']; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_completion_get_course_completion_status', data, preSets).then((data) => { |             return site.read('core_completion_get_course_completion_status', data, preSets) | ||||||
|  |                     .then((data: AddonCourseCompletionGetCourseCompletionStatusResult): any => { | ||||||
|  | 
 | ||||||
|                 if (data.completionstatus) { |                 if (data.completionstatus) { | ||||||
|                     return data.completionstatus; |                     return data.completionstatus; | ||||||
|                 } |                 } | ||||||
| @ -243,17 +248,56 @@ export class AddonCourseCompletionProvider { | |||||||
|      * Mark a course as self completed. |      * Mark a course as self completed. | ||||||
|      * |      * | ||||||
|      * @param courseId Course ID. |      * @param courseId Course ID. | ||||||
|      * @return Resolved on success. |      * @return Promise resolved on success. | ||||||
|      */ |      */ | ||||||
|     markCourseAsSelfCompleted(courseId: number): Promise<any> { |     markCourseAsSelfCompleted(courseId: number): Promise<void> { | ||||||
|         const params = { |         const params = { | ||||||
|             courseid: courseId |             courseid: courseId | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getCurrentSite().write('core_completion_mark_course_self_completed', params).then((response) => { |         return this.sitesProvider.getCurrentSite().write('core_completion_mark_course_self_completed', params) | ||||||
|  |                 .then((response: AddonCourseCompletionMarkCourseSelfCompletedResult) => { | ||||||
|  | 
 | ||||||
|             if (!response.status) { |             if (!response.status) { | ||||||
|                 return Promise.reject(null); |                 return Promise.reject(null); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Completion status returned by core_completion_get_course_completion_status. | ||||||
|  |  */ | ||||||
|  | export type AddonCourseCompletionCourseCompletionStatus = { | ||||||
|  |     completed: boolean; // True if the course is complete, false otherwise.
 | ||||||
|  |     aggregation: number; // Aggregation method 1 means all, 2 means any.
 | ||||||
|  |     completions: { | ||||||
|  |         type: number; // Completion criteria type.
 | ||||||
|  |         title: string; // Completion criteria Title.
 | ||||||
|  |         status: string; // Completion status (Yes/No) a % or number.
 | ||||||
|  |         complete: boolean; // Completion status (true/false).
 | ||||||
|  |         timecompleted: number; // Timestamp for criteria completetion.
 | ||||||
|  |         details: { | ||||||
|  |             type: string; // Type description.
 | ||||||
|  |             criteria: string; // Criteria description.
 | ||||||
|  |             requirement: string; // Requirement description.
 | ||||||
|  |             status: string; // Status description, can be anything.
 | ||||||
|  |         }; // Details.
 | ||||||
|  |     }[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_completion_get_course_completion_status. | ||||||
|  |  */ | ||||||
|  | export type AddonCourseCompletionGetCourseCompletionStatusResult = { | ||||||
|  |     completionstatus: AddonCourseCompletionCourseCompletionStatus; // Course status.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_completion_mark_course_self_completed. | ||||||
|  |  */ | ||||||
|  | export type AddonCourseCompletionMarkCourseSelfCompletedResult = { | ||||||
|  |     status: boolean; // Status, true if success.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import { CoreEventsProvider } from '@providers/events'; | |||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||||
| import { AddonFilesProvider } from '../../providers/files'; | import { AddonFilesProvider, AddonFilesFile, AddonFilesGetUserPrivateFilesInfoResult } from '../../providers/files'; | ||||||
| import { AddonFilesHelperProvider } from '../../providers/helper'; | import { AddonFilesHelperProvider } from '../../providers/helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -40,10 +40,10 @@ export class AddonFilesListPage implements OnDestroy { | |||||||
|     root: string; // The root of the files loaded: 'my' or 'site'.
 |     root: string; // The root of the files loaded: 'my' or 'site'.
 | ||||||
|     path: string; // The path of the directory being loaded. If empty path it means the root is being loaded.
 |     path: string; // The path of the directory being loaded. If empty path it means the root is being loaded.
 | ||||||
|     userQuota: number; // The user quota (in bytes).
 |     userQuota: number; // The user quota (in bytes).
 | ||||||
|     filesInfo: any; // Info about private files (size, number of files, etc.).
 |     filesInfo: AddonFilesGetUserPrivateFilesInfoResult; // Info about private files (size, number of files, etc.).
 | ||||||
|     spaceUsed: string; // Space used in a readable format.
 |     spaceUsed: string; // Space used in a readable format.
 | ||||||
|     userQuotaReadable: string; // User quota in a readable format.
 |     userQuotaReadable: string; // User quota in a readable format.
 | ||||||
|     files: any[]; // List of files.
 |     files: AddonFilesFile[]; // List of files.
 | ||||||
|     component: string; // Component to link the file downloads to.
 |     component: string; // Component to link the file downloads to.
 | ||||||
|     filesLoaded: boolean; // Whether the files are loaded.
 |     filesLoaded: boolean; // Whether the files are loaded.
 | ||||||
| 
 | 
 | ||||||
| @ -147,7 +147,7 @@ export class AddonFilesListPage implements OnDestroy { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected fetchFiles(): Promise<any> { |     protected fetchFiles(): Promise<any> { | ||||||
|         let promise; |         let promise: Promise<AddonFilesFile[]>; | ||||||
| 
 | 
 | ||||||
|         if (!this.path) { |         if (!this.path) { | ||||||
|             // The path is unknown, the user must be requesting a root.
 |             // The path is unknown, the user must be requesting a root.
 | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; | |||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; | import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
|  | import { CoreWSExternalWarning } from '@providers/ws'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to handle my files and site files. |  * Service to handle my files and site files. | ||||||
| @ -73,7 +74,7 @@ export class AddonFilesProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved with the files. |      * @return Promise resolved with the files. | ||||||
|      */ |      */ | ||||||
|     getFiles(params: any, siteId?: string): Promise<any[]> { |     getFiles(params: any, siteId?: string): Promise<AddonFilesFile[]> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const preSets = { |             const preSets = { | ||||||
| @ -82,15 +83,15 @@ export class AddonFilesProvider { | |||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_files_get_files', params, preSets); |             return site.read('core_files_get_files', params, preSets); | ||||||
|         }).then((result) => { |         }).then((result: AddonFilesGetFilesResult) => { | ||||||
|             const entries = []; |             const entries: AddonFilesFile[] = []; | ||||||
| 
 | 
 | ||||||
|             if (result.files) { |             if (result.files) { | ||||||
|                 result.files.forEach((entry) => { |                 result.files.forEach((entry) => { | ||||||
|                     if (entry.isdir) { |                     if (entry.isdir) { | ||||||
|                         // Create a "link" to load the folder.
 |                         // Create a "link" to load the folder.
 | ||||||
|                         entry.link = { |                         entry.link = { | ||||||
|                             contextid: entry.contextid || '', |                             contextid: entry.contextid || null, | ||||||
|                             component: entry.component || '', |                             component: entry.component || '', | ||||||
|                             filearea: entry.filearea || '', |                             filearea: entry.filearea || '', | ||||||
|                             itemid: entry.itemid || 0, |                             itemid: entry.itemid || 0, | ||||||
| @ -135,7 +136,7 @@ export class AddonFilesProvider { | |||||||
|      * |      * | ||||||
|      * @return Promise resolved with the files. |      * @return Promise resolved with the files. | ||||||
|      */ |      */ | ||||||
|     getPrivateFiles(): Promise<any[]> { |     getPrivateFiles(): Promise<AddonFilesFile[]> { | ||||||
|         return this.getFiles(this.getPrivateFilesRootParams()); |         return this.getFiles(this.getPrivateFilesRootParams()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -164,7 +165,7 @@ export class AddonFilesProvider { | |||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Promise resolved with the info. |      * @return Promise resolved with the info. | ||||||
|      */ |      */ | ||||||
|     getPrivateFilesInfo(userId?: number, siteId?: string): Promise<any> { |     getPrivateFilesInfo(userId?: number, siteId?: string): Promise<AddonFilesGetUserPrivateFilesInfoResult> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| 
 | 
 | ||||||
| @ -204,7 +205,7 @@ export class AddonFilesProvider { | |||||||
|      * |      * | ||||||
|      * @return Promise resolved with the files. |      * @return Promise resolved with the files. | ||||||
|      */ |      */ | ||||||
|     getSiteFiles(): Promise<any[]> { |     getSiteFiles(): Promise<AddonFilesFile[]> { | ||||||
|         return this.getFiles(this.getSiteFilesRootParams()); |         return this.getFiles(this.getSiteFilesRootParams()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -388,7 +389,7 @@ export class AddonFilesProvider { | |||||||
|      * @param siteid ID of the site. If not defined, use current site. |      * @param siteid ID of the site. If not defined, use current site. | ||||||
|      * @return Promise resolved in success, rejected otherwise. |      * @return Promise resolved in success, rejected otherwise. | ||||||
|      */ |      */ | ||||||
|     moveFromDraftToPrivate(draftId: number, siteId?: string): Promise<any> { |     moveFromDraftToPrivate(draftId: number, siteId?: string): Promise<null> { | ||||||
|         const params = { |         const params = { | ||||||
|                 draftid: draftId |                 draftid: draftId | ||||||
|             }, |             }, | ||||||
| @ -414,3 +415,63 @@ export class AddonFilesProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * File data returned by core_files_get_files. | ||||||
|  |  */ | ||||||
|  | export type AddonFilesFile = { | ||||||
|  |     contextid: number; | ||||||
|  |     component: string; | ||||||
|  |     filearea: string; | ||||||
|  |     itemid: number; | ||||||
|  |     filepath: string; | ||||||
|  |     filename: string; | ||||||
|  |     isdir: boolean; | ||||||
|  |     url: string; | ||||||
|  |     timemodified: number; | ||||||
|  |     timecreated?: number; // Time created.
 | ||||||
|  |     filesize?: number; // File size.
 | ||||||
|  |     author?: string; // File owner.
 | ||||||
|  |     license?: string; // File license.
 | ||||||
|  | } & AddonFilesFileCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_files_get_files. | ||||||
|  |  */ | ||||||
|  | export type AddonFilesGetFilesResult = { | ||||||
|  |     parents: { | ||||||
|  |         contextid: number; | ||||||
|  |         component: string; | ||||||
|  |         filearea: string; | ||||||
|  |         itemid: number; | ||||||
|  |         filepath: string; | ||||||
|  |         filename: string; | ||||||
|  |     }[]; | ||||||
|  |     files: AddonFilesFile[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_user_get_private_files_info. | ||||||
|  |  */ | ||||||
|  | export type AddonFilesGetUserPrivateFilesInfoResult = { | ||||||
|  |     filecount: number; // Number of files in the area.
 | ||||||
|  |     foldercount: number; // Number of folders in the area.
 | ||||||
|  |     filesize: number; // Total size of the files in the area.
 | ||||||
|  |     filesizewithoutreferences: number; // Total size of the area excluding file references.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Calculated data for AddonFilesFile. | ||||||
|  |  */ | ||||||
|  | export type AddonFilesFileCalculatedData = { | ||||||
|  |     link?: { // Calculated in the app. A link to open the folder.
 | ||||||
|  |         contextid?: number; // Folder's contextid.
 | ||||||
|  |         component?: string; // Folder's component.
 | ||||||
|  |         filearea?: string; // Folder's filearea.
 | ||||||
|  |         itemid?: number; // Folder's itemid.
 | ||||||
|  |         filepath?: string; // Folder's filepath.
 | ||||||
|  |         filename?: string; // Folder's filename.
 | ||||||
|  |     }; | ||||||
|  |     imgPath?: string; // Path to file icon's image.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Component, OnDestroy } from '@angular/core'; | |||||||
| import { IonicPage } from 'ionic-angular'; | import { IonicPage } from 'ionic-angular'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | ||||||
| import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifier'; | import { AddonMessageOutputAirnotifierProvider, AddonMessageOutputAirnotifierDevice } from '../../providers/airnotifier'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays the list of devices. |  * Page that displays the list of devices. | ||||||
| @ -28,7 +28,7 @@ import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifi | |||||||
| }) | }) | ||||||
| export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { | export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { | ||||||
| 
 | 
 | ||||||
|     devices = []; |     devices: AddonMessageOutputAirnotifierDeviceFormatted[] = []; | ||||||
|     devicesLoaded = false; |     devicesLoaded = false; | ||||||
| 
 | 
 | ||||||
|     protected updateTimeout: any; |     protected updateTimeout: any; | ||||||
| @ -54,7 +54,7 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { | |||||||
|             const pushId = this.pushNotificationsProvider.getPushId(); |             const pushId = this.pushNotificationsProvider.getPushId(); | ||||||
| 
 | 
 | ||||||
|             // Convert enabled to boolean and search current device.
 |             // Convert enabled to boolean and search current device.
 | ||||||
|             devices.forEach((device) => { |             devices.forEach((device: AddonMessageOutputAirnotifierDeviceFormatted) => { | ||||||
|                 device.enable = !!device.enable; |                 device.enable = !!device.enable; | ||||||
|                 device.current = pushId && pushId == device.pushid; |                 device.current = pushId && pushId == device.pushid; | ||||||
|             }); |             }); | ||||||
| @ -110,8 +110,9 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { | |||||||
|      * @param device The device object. |      * @param device The device object. | ||||||
|      * @param enable True to enable the device, false to disable it. |      * @param enable True to enable the device, false to disable it. | ||||||
|      */ |      */ | ||||||
|     enableDevice(device: any, enable: boolean): void { |     enableDevice(device: AddonMessageOutputAirnotifierDeviceFormatted, enable: boolean): void { | ||||||
|         device.updating = true; |         device.updating = true; | ||||||
|  | 
 | ||||||
|         this.airnotifierProivder.enableDevice(device.id, enable).then(() => { |         this.airnotifierProivder.enableDevice(device.id, enable).then(() => { | ||||||
|             // Update the list of devices since it was modified.
 |             // Update the list of devices since it was modified.
 | ||||||
|             this.updateDevicesAfterDelay(); |             this.updateDevicesAfterDelay(); | ||||||
| @ -135,3 +136,11 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * User device with some calculated data. | ||||||
|  |  */ | ||||||
|  | type AddonMessageOutputAirnotifierDeviceFormatted = AddonMessageOutputAirnotifierDevice & { | ||||||
|  |     current?: boolean; // Calculated in the app. Whether it's the current device.
 | ||||||
|  |     updating?: boolean; // Calculated in the app. Whether the device enable is being updated right now.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; | |||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreConfigConstants } from '../../../../configconstants'; | import { CoreConfigConstants } from '../../../../configconstants'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
|  | import { CoreWSExternalWarning } from '@providers/ws'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to handle Airnotifier message output. |  * Service to handle Airnotifier message output. | ||||||
| @ -39,14 +40,16 @@ export class AddonMessageOutputAirnotifierProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved if success. |      * @return Promise resolved if success. | ||||||
|      */ |      */ | ||||||
|     enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise<any> { |     enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise<void> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const data = { |             const data = { | ||||||
|                 deviceid: deviceId, |                 deviceid: deviceId, | ||||||
|                 enable: enable ? 1 : 0 |                 enable: enable ? 1 : 0 | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.write('message_airnotifier_enable_device', data).then((result) => { |             return site.write('message_airnotifier_enable_device', data) | ||||||
|  |                     .then((result: AddonMessageOutputAirnotifierEnableDeviceResult) => { | ||||||
|  | 
 | ||||||
|                 if (!result.success) { |                 if (!result.success) { | ||||||
|                     // Fail. Reject with warning message if any.
 |                     // Fail. Reject with warning message if any.
 | ||||||
|                     if (result.warnings && result.warnings.length) { |                     if (result.warnings && result.warnings.length) { | ||||||
| @ -74,7 +77,7 @@ export class AddonMessageOutputAirnotifierProvider { | |||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Promise resolved with the devices. |      * @return Promise resolved with the devices. | ||||||
|      */ |      */ | ||||||
|     getUserDevices(siteId?: string): Promise<any> { |     getUserDevices(siteId?: string): Promise<AddonMessageOutputAirnotifierDevice[]> { | ||||||
|         this.logger.debug('Get user devices'); |         this.logger.debug('Get user devices'); | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| @ -86,7 +89,8 @@ export class AddonMessageOutputAirnotifierProvider { | |||||||
|                 updateFrequency: CoreSite.FREQUENCY_RARELY |                 updateFrequency: CoreSite.FREQUENCY_RARELY | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.read('message_airnotifier_get_user_devices', data, preSets).then((data) => { |             return site.read('message_airnotifier_get_user_devices', data, preSets) | ||||||
|  |                     .then((data: AddonMessageOutputAirnotifierGetUserDevicesResult) => { | ||||||
|                 return data.devices; |                 return data.devices; | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
| @ -115,3 +119,36 @@ export class AddonMessageOutputAirnotifierProvider { | |||||||
|                 this.sitesProvider.wsAvailableInCurrentSite('message_airnotifier_get_user_devices'); |                 this.sitesProvider.wsAvailableInCurrentSite('message_airnotifier_get_user_devices'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Device data returned by WS message_airnotifier_get_user_devices. | ||||||
|  |  */ | ||||||
|  | export type AddonMessageOutputAirnotifierDevice = { | ||||||
|  |     id: number; // Device id (in the message_airnotifier table).
 | ||||||
|  |     appid: string; // The app id, something like com.moodle.moodlemobile.
 | ||||||
|  |     name: string; // The device name, 'occam' or 'iPhone' etc.
 | ||||||
|  |     model: string; // The device model 'Nexus4' or 'iPad1,1' etc.
 | ||||||
|  |     platform: string; // The device platform 'iOS' or 'Android' etc.
 | ||||||
|  |     version: string; // The device version '6.1.2' or '4.2.2' etc.
 | ||||||
|  |     pushid: string; // The device PUSH token/key/identifier/registration id.
 | ||||||
|  |     uuid: string; // The device UUID.
 | ||||||
|  |     enable: number | boolean; // Whether the device is enabled or not.
 | ||||||
|  |     timecreated: number; // Time created.
 | ||||||
|  |     timemodified: number; // Time modified.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS message_airnotifier_enable_device. | ||||||
|  |  */ | ||||||
|  | export type AddonMessageOutputAirnotifierEnableDeviceResult = { | ||||||
|  |     success: boolean; // True if success.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS message_airnotifier_get_user_devices. | ||||||
|  |  */ | ||||||
|  | export type AddonMessageOutputAirnotifierGetUserDevicesResult = { | ||||||
|  |     devices: AddonMessageOutputAirnotifierDevice[]; // List of devices.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@ | |||||||
| import { Content } from 'ionic-angular'; | import { Content } from 'ionic-angular'; | ||||||
| import { CoreEventsProvider } from '@providers/events'; | import { CoreEventsProvider } from '@providers/events'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { AddonMessagesProvider } from '../../providers/messages'; | import { AddonMessagesProvider, AddonMessagesConversationMember } from '../../providers/messages'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -33,7 +33,7 @@ export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestro | |||||||
|     loaded = false; |     loaded = false; | ||||||
|     canLoadMore = false; |     canLoadMore = false; | ||||||
|     loadMoreError = false; |     loadMoreError = false; | ||||||
|     contacts = []; |     contacts: AddonMessagesConversationMember[] = []; | ||||||
|     selectedUserId: number; |     selectedUserId: number; | ||||||
| 
 | 
 | ||||||
|     protected memberInfoObserver; |     protected memberInfoObserver; | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@ | |||||||
| import { Content } from 'ionic-angular'; | import { Content } from 'ionic-angular'; | ||||||
| import { CoreEventsProvider } from '@providers/events'; | import { CoreEventsProvider } from '@providers/events'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { AddonMessagesProvider } from '../../providers/messages'; | import { AddonMessagesProvider, AddonMessagesConversationMember } from '../../providers/messages'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -33,7 +33,7 @@ export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy | |||||||
|     loaded = false; |     loaded = false; | ||||||
|     canLoadMore = false; |     canLoadMore = false; | ||||||
|     loadMoreError = false; |     loadMoreError = false; | ||||||
|     requests = []; |     requests: AddonMessagesConversationMember[] = []; | ||||||
|     selectedUserId: number; |     selectedUserId: number; | ||||||
| 
 | 
 | ||||||
|     protected memberInfoObserver; |     protected memberInfoObserver; | ||||||
|  | |||||||
| @ -16,7 +16,9 @@ import { Component } from '@angular/core'; | |||||||
| import { NavParams } from 'ionic-angular'; | import { NavParams } from 'ionic-angular'; | ||||||
| import { TranslateService } from '@ngx-translate/core'; | import { TranslateService } from '@ngx-translate/core'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { AddonMessagesProvider } from '../../providers/messages'; | import { | ||||||
|  |     AddonMessagesProvider, AddonMessagesGetContactsResult, AddonMessagesSearchContactsContact | ||||||
|  | } from '../../providers/messages'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreAppProvider } from '@providers/app'; | import { CoreAppProvider } from '@providers/app'; | ||||||
| import { CoreEventsProvider } from '@providers/events'; | import { CoreEventsProvider } from '@providers/events'; | ||||||
| @ -42,7 +44,10 @@ export class AddonMessagesContactsComponent { | |||||||
|     searchType = 'search'; |     searchType = 'search'; | ||||||
|     loadingMessage = ''; |     loadingMessage = ''; | ||||||
|     hasContacts = false; |     hasContacts = false; | ||||||
|     contacts = { |     contacts: AddonMessagesGetContactsFormatted = { | ||||||
|  |         online: [], | ||||||
|  |         offline: [], | ||||||
|  |         strangers: [], | ||||||
|         search: [] |         search: [] | ||||||
|     }; |     }; | ||||||
|     searchString = ''; |     searchString = ''; | ||||||
| @ -205,7 +210,7 @@ export class AddonMessagesContactsComponent { | |||||||
|             this.searchString = query; |             this.searchString = query; | ||||||
|             this.contactTypes = ['search']; |             this.contactTypes = ['search']; | ||||||
| 
 | 
 | ||||||
|             this.contacts['search'] = this.sortUsers(result); |             this.contacts.search = this.sortUsers(result); | ||||||
|         }).catch((error) => { |         }).catch((error) => { | ||||||
|             this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true); |             this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true); | ||||||
|         }); |         }); | ||||||
| @ -234,3 +239,10 @@ export class AddonMessagesContactsComponent { | |||||||
|         this.memberInfoObserver && this.memberInfoObserver.off(); |         this.memberInfoObserver && this.memberInfoObserver.off(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Contacts with some calculated data. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetContactsFormatted = AddonMessagesGetContactsResult & { | ||||||
|  |     search?: AddonMessagesSearchContactsContact[]; // Calculated in the app. Result of searching users.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -14,7 +14,9 @@ | |||||||
| 
 | 
 | ||||||
| import { Component, OnInit } from '@angular/core'; | import { Component, OnInit } from '@angular/core'; | ||||||
| import { IonicPage, NavParams, ViewController } from 'ionic-angular'; | import { IonicPage, NavParams, ViewController } from 'ionic-angular'; | ||||||
| import { AddonMessagesProvider } from '../../providers/messages'; | import { | ||||||
|  |     AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMember | ||||||
|  | } from '../../providers/messages'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -28,8 +30,8 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; | |||||||
| export class AddonMessagesConversationInfoPage implements OnInit { | export class AddonMessagesConversationInfoPage implements OnInit { | ||||||
| 
 | 
 | ||||||
|     loaded = false; |     loaded = false; | ||||||
|     conversation: any; |     conversation: AddonMessagesConversationFormatted; | ||||||
|     members = []; |     members: AddonMessagesConversationMember[] = []; | ||||||
|     canLoadMore = false; |     canLoadMore = false; | ||||||
|     loadMoreError = false; |     loadMoreError = false; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -17,7 +17,10 @@ import { IonicPage, NavParams, NavController, Content, ModalController } from 'i | |||||||
| import { TranslateService } from '@ngx-translate/core'; | import { TranslateService } from '@ngx-translate/core'; | ||||||
| import { CoreEventsProvider } from '@providers/events'; | import { CoreEventsProvider } from '@providers/events'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { AddonMessagesProvider } from '../../providers/messages'; | import { | ||||||
|  |     AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMember, AddonMessagesConversationMessage, | ||||||
|  |     AddonMessagesGetMessagesMessage | ||||||
|  | } from '../../providers/messages'; | ||||||
| import { AddonMessagesOfflineProvider } from '../../providers/messages-offline'; | import { AddonMessagesOfflineProvider } from '../../providers/messages-offline'; | ||||||
| import { AddonMessagesSyncProvider } from '../../providers/sync'; | import { AddonMessagesSyncProvider } from '../../providers/sync'; | ||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| @ -54,7 +57,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|     protected messagesBeingSent = 0; |     protected messagesBeingSent = 0; | ||||||
|     protected pagesLoaded = 1; |     protected pagesLoaded = 1; | ||||||
|     protected lastMessage = {text: '', timecreated: 0}; |     protected lastMessage = {text: '', timecreated: 0}; | ||||||
|     protected keepMessageMap = {}; |     protected keepMessageMap: {[hash: string]: boolean} = {}; | ||||||
|     protected syncObserver: any; |     protected syncObserver: any; | ||||||
|     protected oldContentHeight = 0; |     protected oldContentHeight = 0; | ||||||
|     protected keyboardObserver: any; |     protected keyboardObserver: any; | ||||||
| @ -64,7 +67,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|     protected showLoadingModal = false; // Whether to show a loading modal while fetching data.
 |     protected showLoadingModal = false; // Whether to show a loading modal while fetching data.
 | ||||||
| 
 | 
 | ||||||
|     conversationId: number; // Conversation ID. Undefined if it's a new individual conversation.
 |     conversationId: number; // Conversation ID. Undefined if it's a new individual conversation.
 | ||||||
|     conversation: any; // The conversation object (if it exists).
 |     conversation: AddonMessagesConversationFormatted; // The conversation object (if it exists).
 | ||||||
|     userId: number; // User ID you're talking to (only if group messaging not enabled or it's a new individual conversation).
 |     userId: number; // User ID you're talking to (only if group messaging not enabled or it's a new individual conversation).
 | ||||||
|     currentUserId: number; |     currentUserId: number; | ||||||
|     title: string; |     title: string; | ||||||
| @ -74,18 +77,18 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|     showKeyboard = false; |     showKeyboard = false; | ||||||
|     canLoadMore = false; |     canLoadMore = false; | ||||||
|     loadMoreError = false; |     loadMoreError = false; | ||||||
|     messages = []; |     messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[] = []; | ||||||
|     showDelete = false; |     showDelete = false; | ||||||
|     canDelete = false; |     canDelete = false; | ||||||
|     groupMessagingEnabled: boolean; |     groupMessagingEnabled: boolean; | ||||||
|     isGroup = false; |     isGroup = false; | ||||||
|     members: any = {}; // Members that wrote a message, indexed by ID.
 |     members: {[id: number]: AddonMessagesConversationMember} = {}; // Members that wrote a message, indexed by ID.
 | ||||||
|     favouriteIcon = 'fa-star'; |     favouriteIcon = 'fa-star'; | ||||||
|     favouriteIconSlash = false; |     favouriteIconSlash = false; | ||||||
|     deleteIcon = 'trash'; |     deleteIcon = 'trash'; | ||||||
|     blockIcon = 'close-circle'; |     blockIcon = 'close-circle'; | ||||||
|     addRemoveIcon = 'person'; |     addRemoveIcon = 'person'; | ||||||
|     otherMember: any; // Other member information (individual conversations only).
 |     otherMember: AddonMessagesConversationMember; // Other member information (individual conversations only).
 | ||||||
|     footerType: 'message' | 'blocked' | 'requiresContact' | 'requestSent' | 'requestReceived' | 'unable'; |     footerType: 'message' | 'blocked' | 'requiresContact' | 'requestSent' | 'requestReceived' | 'unable'; | ||||||
|     requestContactSent = false; |     requestContactSent = false; | ||||||
|     requestContactReceived = false; |     requestContactReceived = false; | ||||||
| @ -139,7 +142,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @param message Message to be added. |      * @param message Message to be added. | ||||||
|      * @param keep If set the keep flag or not. |      * @param keep If set the keep flag or not. | ||||||
|      */ |      */ | ||||||
|     protected addMessage(message: any, keep: boolean = true): void { |     protected addMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, | ||||||
|  |             keep: boolean = true): void { | ||||||
|  | 
 | ||||||
|         /* Create a hash to identify the message. The text of online messages isn't reliable because it can have random data |         /* Create a hash to identify the message. The text of online messages isn't reliable because it can have random data | ||||||
|            like VideoJS ID. Try to use id and fallback to text for offline messages. */ |            like VideoJS ID. Try to use id and fallback to text for offline messages. */ | ||||||
|         message.hash = Md5.hashAsciiStr(String(message.id || message.text || '')) + '#' + message.timecreated + '#' + |         message.hash = Md5.hashAsciiStr(String(message.id || message.text || '')) + '#' + message.timecreated + '#' + | ||||||
| @ -158,7 +163,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * |      * | ||||||
|      * @param hash Hash of the message to be removed. |      * @param hash Hash of the message to be removed. | ||||||
|      */ |      */ | ||||||
|     protected removeMessage(hash: any): void { |     protected removeMessage(hash: string): void { | ||||||
|         if (this.keepMessageMap[hash]) { |         if (this.keepMessageMap[hash]) { | ||||||
|             // Selected to keep it, clear the flag.
 |             // Selected to keep it, clear the flag.
 | ||||||
|             this.keepMessageMap[hash] = false; |             this.keepMessageMap[hash] = false; | ||||||
| @ -261,10 +266,11 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|                     if (!this.title && this.messages.length) { |                     if (!this.title && this.messages.length) { | ||||||
|                         // Didn't receive the fullname via argument. Try to get it from messages.
 |                         // Didn't receive the fullname via argument. Try to get it from messages.
 | ||||||
|                         // It's possible that name cannot be resolved when no messages were yet exchanged.
 |                         // It's possible that name cannot be resolved when no messages were yet exchanged.
 | ||||||
|                         if (this.messages[0].useridto != this.currentUserId) { |                         const firstMessage = <AddonMessagesGetMessagesMessageFormatted> this.messages[0]; | ||||||
|                             this.title = this.messages[0].usertofullname || ''; |                         if (firstMessage.useridto != this.currentUserId) { | ||||||
|  |                             this.title = firstMessage.usertofullname || ''; | ||||||
|                         } else { |                         } else { | ||||||
|                             this.title = this.messages[0].userfromfullname || ''; |                             this.title = firstMessage.userfromfullname || ''; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 }); |                 }); | ||||||
| @ -302,7 +308,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * |      * | ||||||
|      * @return Resolved when done. |      * @return Resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected fetchMessages(): Promise<any> { |     protected fetchMessages(): Promise<void> { | ||||||
|         this.loadMoreError = false; |         this.loadMoreError = false; | ||||||
| 
 | 
 | ||||||
|         if (this.messagesBeingSent > 0) { |         if (this.messagesBeingSent > 0) { | ||||||
| @ -341,7 +347,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|                     return this.getDiscussionMessages(this.pagesLoaded); |                     return this.getDiscussionMessages(this.pagesLoaded); | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|         }).then((messages) => { |         }).then((messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) => { | ||||||
|             this.loadMessages(messages); |             this.loadMessages(messages); | ||||||
|         }).finally(() => { |         }).finally(() => { | ||||||
|             this.fetching = false; |             this.fetching = false; | ||||||
| @ -353,7 +359,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * |      * | ||||||
|      * @param messages Messages to load. |      * @param messages Messages to load. | ||||||
|      */ |      */ | ||||||
|     protected loadMessages(messages: any[]): void { |     protected loadMessages(messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) | ||||||
|  |             : void { | ||||||
|  | 
 | ||||||
|         if (this.viewDestroyed) { |         if (this.viewDestroyed) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -382,7 +390,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|         this.messagesProvider.sortMessages(this.messages); |         this.messagesProvider.sortMessages(this.messages); | ||||||
| 
 | 
 | ||||||
|         // Calculate which messages need to display the date or user data.
 |         // Calculate which messages need to display the date or user data.
 | ||||||
|         this.messages.forEach((message, index): any => { |         this.messages.forEach((message, index) => { | ||||||
|             message.showDate = this.showDate(message, this.messages[index - 1]); |             message.showDate = this.showDate(message, this.messages[index - 1]); | ||||||
|             message.showUserData = this.showUserData(message, this.messages[index - 1]); |             message.showUserData = this.showUserData(message, this.messages[index - 1]); | ||||||
|             message.showTail = this.showTail(message, this.messages[index + 1]); |             message.showTail = this.showTail(message, this.messages[index + 1]); | ||||||
| @ -411,20 +419,22 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @return Promise resolved with a boolean: whether the conversation exists or not. |      * @return Promise resolved with a boolean: whether the conversation exists or not. | ||||||
|      */ |      */ | ||||||
|     protected getConversation(conversationId: number, userId: number): Promise<boolean> { |     protected getConversation(conversationId: number, userId: number): Promise<boolean> { | ||||||
|         let promise, |         let promise: Promise<number>, | ||||||
|             fallbackConversation; |             fallbackConversation: AddonMessagesConversationFormatted; | ||||||
| 
 | 
 | ||||||
|         // Try to get the conversationId if we don't have it.
 |         // Try to get the conversationId if we don't have it.
 | ||||||
|         if (conversationId) { |         if (conversationId) { | ||||||
|             promise = Promise.resolve(conversationId); |             promise = Promise.resolve(conversationId); | ||||||
|         } else { |         } else { | ||||||
|  |             let subPromise: Promise<AddonMessagesConversationFormatted>; | ||||||
|  | 
 | ||||||
|             if (userId == this.currentUserId && this.messagesProvider.isSelfConversationEnabled()) { |             if (userId == this.currentUserId && this.messagesProvider.isSelfConversationEnabled()) { | ||||||
|                 promise = this.messagesProvider.getSelfConversation(); |                 subPromise = this.messagesProvider.getSelfConversation(); | ||||||
|             } else { |             } else { | ||||||
|                 promise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true); |                 subPromise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             promise = promise.then((conversation) => { |             promise = subPromise.then((conversation) => { | ||||||
|                 fallbackConversation = conversation; |                 fallbackConversation = conversation; | ||||||
| 
 | 
 | ||||||
|                 return conversation.id; |                 return conversation.id; | ||||||
| @ -437,14 +447,14 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|                 // Ignore errors.
 |                 // Ignore errors.
 | ||||||
|             }).then(() => { |             }).then(() => { | ||||||
|                 return this.messagesProvider.getConversation(conversationId, undefined, true); |                 return this.messagesProvider.getConversation(conversationId, undefined, true); | ||||||
|             }).catch((error) => { |             }).catch((error): any => { | ||||||
|                 // Get conversation failed, use the fallback one if we have it.
 |                 // Get conversation failed, use the fallback one if we have it.
 | ||||||
|                 if (fallbackConversation) { |                 if (fallbackConversation) { | ||||||
|                     return fallbackConversation; |                     return fallbackConversation; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 return Promise.reject(error); |                 return Promise.reject(error); | ||||||
|             }).then((conversation) => { |             }).then((conversation: AddonMessagesConversationFormatted) => { | ||||||
|                 this.conversation = conversation; |                 this.conversation = conversation; | ||||||
| 
 | 
 | ||||||
|                 if (conversation) { |                 if (conversation) { | ||||||
| @ -495,7 +505,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @param offset Offset for message list. |      * @param offset Offset for message list. | ||||||
|      * @return Promise resolved with the list of messages. |      * @return Promise resolved with the list of messages. | ||||||
|      */ |      */ | ||||||
|     protected getConversationMessages(pagesToLoad: number, offset: number = 0): Promise<any[]> { |     protected getConversationMessages(pagesToLoad: number, offset: number = 0) | ||||||
|  |             : Promise<AddonMessagesConversationMessageFormatted[]> { | ||||||
|  | 
 | ||||||
|         const excludePending = offset > 0; |         const excludePending = offset > 0; | ||||||
| 
 | 
 | ||||||
|         return this.messagesProvider.getConversationMessages(this.conversationId, excludePending, offset).then((result) => { |         return this.messagesProvider.getConversationMessages(this.conversationId, excludePending, offset).then((result) => { | ||||||
| @ -535,7 +547,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @return Resolved when done. |      * @return Resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected getDiscussionMessages(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, |     protected getDiscussionMessages(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, | ||||||
|             lfSentUnread: number = 0, lfSentRead: number = 0): Promise<any> { |             lfSentUnread: number = 0, lfSentRead: number = 0): Promise<AddonMessagesGetMessagesMessageFormatted[]> { | ||||||
| 
 | 
 | ||||||
|         // Only get offline messages if we're loading the first "page".
 |         // Only get offline messages if we're loading the first "page".
 | ||||||
|         const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0; |         const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0; | ||||||
| @ -547,7 +559,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|             pagesToLoad--; |             pagesToLoad--; | ||||||
|             if (pagesToLoad > 0 && result.canLoadMore) { |             if (pagesToLoad > 0 && result.canLoadMore) { | ||||||
|                 // More pages to load. Calculate new limit froms.
 |                 // More pages to load. Calculate new limit froms.
 | ||||||
|                 result.messages.forEach((message) => { |                 result.messages.forEach((message: AddonMessagesGetMessagesMessageFormatted) => { | ||||||
|                     if (!message.pending) { |                     if (!message.pending) { | ||||||
|                         if (message.useridfrom == this.userId) { |                         if (message.useridfrom == this.userId) { | ||||||
|                             if (message.read) { |                             if (message.read) { | ||||||
| @ -598,7 +610,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|                 for (const x in this.messages) { |                 for (const x in this.messages) { | ||||||
|                     const message = this.messages[x]; |                     const message = this.messages[x]; | ||||||
|                     // If an unread message is found, mark all messages as read.
 |                     // If an unread message is found, mark all messages as read.
 | ||||||
|                     if (message.useridfrom != this.currentUserId && message.read == 0) { |                     if (message.useridfrom != this.currentUserId && | ||||||
|  |                             (<AddonMessagesGetMessagesMessageFormatted> message).read == 0) { | ||||||
|                         messageUnreadFound = true; |                         messageUnreadFound = true; | ||||||
|                         break; |                         break; | ||||||
|                     } |                     } | ||||||
| @ -616,7 +629,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|                     promise = this.messagesProvider.markAllMessagesRead(this.userId).then(() => { |                     promise = this.messagesProvider.markAllMessagesRead(this.userId).then(() => { | ||||||
|                         // Mark all messages as read.
 |                         // Mark all messages as read.
 | ||||||
|                         this.messages.forEach((message) => { |                         this.messages.forEach((message) => { | ||||||
|                             message.read = 1; |                             (<AddonMessagesGetMessagesMessageFormatted> message).read = 1; | ||||||
|                         }); |                         }); | ||||||
|                     }); |                     }); | ||||||
|                 } |                 } | ||||||
| @ -630,10 +643,10 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|             // Mark each message as read one by one.
 |             // Mark each message as read one by one.
 | ||||||
|             this.messages.forEach((message) => { |             this.messages.forEach((message) => { | ||||||
|                 // If the message is unread, call this.messagesProvider.markMessageRead.
 |                 // If the message is unread, call this.messagesProvider.markMessageRead.
 | ||||||
|                 if (message.useridfrom != this.currentUserId && message.read == 0) { |                 if (message.useridfrom != this.currentUserId && (<AddonMessagesGetMessagesMessageFormatted> message).read == 0) { | ||||||
|                     promises.push(this.messagesProvider.markMessageRead(message.id).then(() => { |                     promises.push(this.messagesProvider.markMessageRead(message.id).then(() => { | ||||||
|                         readChanged = true; |                         readChanged = true; | ||||||
|                         message.read = 1; |                         (<AddonMessagesGetMessagesMessageFormatted> message).read = 1; | ||||||
|                     })); |                     })); | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
| @ -703,7 +716,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|                     if (!message.pending && message.useridfrom != this.currentUserId) { |                     if (!message.pending && message.useridfrom != this.currentUserId) { | ||||||
|                         found++; |                         found++; | ||||||
|                         if (found == this.conversation.unreadcount) { |                         if (found == this.conversation.unreadcount) { | ||||||
|                             this.unreadMessageFrom = parseInt(message.id, 10); |                             this.unreadMessageFrom = Number(message.id); | ||||||
|                             break; |                             break; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| @ -713,13 +726,13 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|             let previousMessageRead = false; |             let previousMessageRead = false; | ||||||
| 
 | 
 | ||||||
|             for (const x in this.messages) { |             for (const x in this.messages) { | ||||||
|                 const message = this.messages[x]; |                 const message = <AddonMessagesGetMessagesMessageFormatted> this.messages[x]; | ||||||
|                 if (message.useridfrom != this.currentUserId) { |                 if (message.useridfrom != this.currentUserId) { | ||||||
|                     const unreadFrom = message.read == 0 && previousMessageRead; |                     const unreadFrom = message.read == 0 && previousMessageRead; | ||||||
| 
 | 
 | ||||||
|                     if (unreadFrom) { |                     if (unreadFrom) { | ||||||
|                         // Save where the label is placed.
 |                         // Save where the label is placed.
 | ||||||
|                         this.unreadMessageFrom = parseInt(message.id, 10); |                         this.unreadMessageFrom = Number(message.id); | ||||||
|                         break; |                         break; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
| @ -808,8 +821,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * |      * | ||||||
|      * @param message Message to be copied. |      * @param message Message to be copied. | ||||||
|      */ |      */ | ||||||
|     copyMessage(message: any): void { |     copyMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): void { | ||||||
|         const text = this.textUtils.decodeHTMLEntities(message.smallmessage || message.text || ''); |         const text = this.textUtils.decodeHTMLEntities( | ||||||
|  |                 (<AddonMessagesGetMessagesMessageFormatted> message).smallmessage || message.text || ''); | ||||||
|         this.utils.copyToClipboard(text); |         this.utils.copyToClipboard(text); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -819,7 +833,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @param message Message object to delete. |      * @param message Message object to delete. | ||||||
|      * @param index Index where the message is to delete it from the view. |      * @param index Index where the message is to delete it from the view. | ||||||
|      */ |      */ | ||||||
|     deleteMessage(message: any, index: number): void { |     deleteMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, index: number) | ||||||
|  |             : void { | ||||||
|  | 
 | ||||||
|         const canDeleteAll = this.conversation && this.conversation.candeletemessagesforallusers, |         const canDeleteAll = this.conversation && this.conversation.candeletemessagesforallusers, | ||||||
|             langKey = message.pending || canDeleteAll || this.isSelf ? 'core.areyousure' : |             langKey = message.pending || canDeleteAll || this.isSelf ? 'core.areyousure' : | ||||||
|                     'addon.messages.deletemessageconfirmation', |                     'addon.messages.deletemessageconfirmation', | ||||||
| @ -860,7 +876,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. |      * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. | ||||||
|      * @return Resolved when done. |      * @return Resolved when done. | ||||||
|      */ |      */ | ||||||
|     loadPrevious(infiniteComplete?: any): Promise<any> { |     loadPrevious(infiniteComplete?: any): Promise<void> { | ||||||
|         let infiniteHeight = this.infinite ? this.infinite.getHeight() : 0; |         let infiniteHeight = this.infinite ? this.infinite.getHeight() : 0; | ||||||
|         const scrollHeight = this.domUtils.getScrollHeight(this.content); |         const scrollHeight = this.domUtils.getScrollHeight(this.content); | ||||||
| 
 | 
 | ||||||
| @ -962,7 +978,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @param text Message text. |      * @param text Message text. | ||||||
|      */ |      */ | ||||||
|     sendMessage(text: string): void { |     sendMessage(text: string): void { | ||||||
|         let message; |         let message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted; | ||||||
| 
 | 
 | ||||||
|         this.hideUnreadLabel(); |         this.hideUnreadLabel(); | ||||||
| 
 | 
 | ||||||
| @ -970,6 +986,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|         this.scrollBottom = true; |         this.scrollBottom = true; | ||||||
| 
 | 
 | ||||||
|         message = { |         message = { | ||||||
|  |             id: null, | ||||||
|             pending: true, |             pending: true, | ||||||
|             sending: true, |             sending: true, | ||||||
|             useridfrom: this.currentUserId, |             useridfrom: this.currentUserId, | ||||||
| @ -985,7 +1002,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|         // If there is an ongoing fetch, wait for it to finish.
 |         // If there is an ongoing fetch, wait for it to finish.
 | ||||||
|         // Otherwise, if a message is sent while fetching it could disappear until the next fetch.
 |         // Otherwise, if a message is sent while fetching it could disappear until the next fetch.
 | ||||||
|         this.waitForFetch().finally(() => { |         this.waitForFetch().finally(() => { | ||||||
|             let promise; |             let promise: Promise<{sent: boolean, message: any}>; | ||||||
| 
 | 
 | ||||||
|             if (this.conversationId) { |             if (this.conversationId) { | ||||||
|                 promise = this.messagesProvider.sendMessageToConversation(this.conversation, text); |                 promise = this.messagesProvider.sendMessageToConversation(this.conversation, text); | ||||||
| @ -1050,7 +1067,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @param prevMessage Previous message where to compare the date with. |      * @param prevMessage Previous message where to compare the date with. | ||||||
|      * @return If date has changed and should be shown. |      * @return If date has changed and should be shown. | ||||||
|      */ |      */ | ||||||
|     showDate(message: any, prevMessage?: any): boolean { |     showDate(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, | ||||||
|  |             prevMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean { | ||||||
|  | 
 | ||||||
|         if (!prevMessage) { |         if (!prevMessage) { | ||||||
|             // First message, show it.
 |             // First message, show it.
 | ||||||
|             return true; |             return true; | ||||||
| @ -1068,7 +1087,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @param prevMessage Previous message. |      * @param prevMessage Previous message. | ||||||
|      * @return Whether user data should be shown. |      * @return Whether user data should be shown. | ||||||
|      */ |      */ | ||||||
|     showUserData(message: any, prevMessage?: any): boolean { |     showUserData(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, | ||||||
|  |             prevMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean { | ||||||
|  | 
 | ||||||
|         return this.isGroup && message.useridfrom != this.currentUserId && this.members[message.useridfrom] && |         return this.isGroup && message.useridfrom != this.currentUserId && this.members[message.useridfrom] && | ||||||
|             (!prevMessage || prevMessage.useridfrom != message.useridfrom || message.showDate); |             (!prevMessage || prevMessage.useridfrom != message.useridfrom || message.showDate); | ||||||
|     } |     } | ||||||
| @ -1080,7 +1101,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|      * @param nextMessage Next message. |      * @param nextMessage Next message. | ||||||
|      * @return Whether user data should be shown. |      * @return Whether user data should be shown. | ||||||
|      */ |      */ | ||||||
|     showTail(message: any, nextMessage?: any): boolean { |     showTail(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, | ||||||
|  |             nextMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean { | ||||||
|         return !nextMessage || nextMessage.useridfrom != message.useridfrom || nextMessage.showDate; |         return !nextMessage || nextMessage.useridfrom != message.useridfrom || nextMessage.showDate; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -1422,3 +1444,26 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | |||||||
|         this.viewDestroyed = true; |         this.viewDestroyed = true; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Conversation message with some calculated data. | ||||||
|  |  */ | ||||||
|  | type AddonMessagesConversationMessageFormatted = AddonMessagesConversationMessage & { | ||||||
|  |     pending?: boolean; // Calculated in the app. Whether the message is pending to be sent.
 | ||||||
|  |     sending?: boolean; // Calculated in the app. Whether the message is being sent right now.
 | ||||||
|  |     hash?: string; // Calculated in the app. A hash to identify the message.
 | ||||||
|  |     showDate?: boolean; // Calculated in the app. Whether to show the date before the message.
 | ||||||
|  |     showUserData?: boolean; // Calculated in the app. Whether to show the user data in the message.
 | ||||||
|  |     showTail?: boolean; // Calculated in the app. Whether to show a "tail" in the message.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Message with some calculated data. | ||||||
|  |  */ | ||||||
|  | type AddonMessagesGetMessagesMessageFormatted = AddonMessagesGetMessagesMessage & { | ||||||
|  |     sending?: boolean; // Calculated in the app. Whether the message is being sent right now.
 | ||||||
|  |     hash?: string; // Calculated in the app. A hash to identify the message.
 | ||||||
|  |     showDate?: boolean; // Calculated in the app. Whether to show the date before the message.
 | ||||||
|  |     showUserData?: boolean; // Calculated in the app. Whether to show the user data in the message.
 | ||||||
|  |     showTail?: boolean; // Calculated in the app. Whether to show a "tail" in the message.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -17,7 +17,9 @@ import { IonicPage, Platform, NavController, NavParams, Content } from 'ionic-an | |||||||
| import { TranslateService } from '@ngx-translate/core'; | import { TranslateService } from '@ngx-translate/core'; | ||||||
| import { CoreEventsProvider } from '@providers/events'; | import { CoreEventsProvider } from '@providers/events'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { AddonMessagesProvider } from '../../providers/messages'; | import { | ||||||
|  |     AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMessage | ||||||
|  | } from '../../providers/messages'; | ||||||
| import { AddonMessagesOfflineProvider } from '../../providers/messages-offline'; | import { AddonMessagesOfflineProvider } from '../../providers/messages-offline'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| @ -45,19 +47,19 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|     selectedConversationId: number; |     selectedConversationId: number; | ||||||
|     selectedUserId: number; |     selectedUserId: number; | ||||||
|     contactRequestsCount = 0; |     contactRequestsCount = 0; | ||||||
|     favourites: any = { |     favourites: AddonMessagesGroupConversationOption = { | ||||||
|         type: null, |         type: null, | ||||||
|         favourites: true, |         favourites: true, | ||||||
|         count: 0, |         count: 0, | ||||||
|         unread: 0 |         unread: 0, | ||||||
|     }; |     }; | ||||||
|     group: any = { |     group: AddonMessagesGroupConversationOption = { | ||||||
|         type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP, |         type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP, | ||||||
|         favourites: false, |         favourites: false, | ||||||
|         count: 0, |         count: 0, | ||||||
|         unread: 0 |         unread: 0 | ||||||
|     }; |     }; | ||||||
|     individual: any = { |     individual: AddonMessagesGroupConversationOption = { | ||||||
|         type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, |         type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, | ||||||
|         favourites: false, |         favourites: false, | ||||||
|         count: 0, |         count: 0, | ||||||
| @ -331,7 +333,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * |      * | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected fetchDataForExpandedOption(): Promise<any> { |     protected fetchDataForExpandedOption(): Promise<void> { | ||||||
|         const expandedOption = this.getExpandedOption(); |         const expandedOption = this.getExpandedOption(); | ||||||
| 
 | 
 | ||||||
|         if (expandedOption) { |         if (expandedOption) { | ||||||
| @ -349,12 +351,12 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * @param getCounts Whether to get counts data. |      * @param getCounts Whether to get counts data. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     fetchDataForOption(option: any, loadingMore?: boolean, getCounts?: boolean): Promise<void> { |     fetchDataForOption(option: AddonMessagesGroupConversationOption, loadingMore?: boolean, getCounts?: boolean): Promise<void> { | ||||||
|         option.loadMoreError = false; |         option.loadMoreError = false; | ||||||
| 
 | 
 | ||||||
|         const limitFrom = loadingMore ? option.conversations.length : 0, |         const limitFrom = loadingMore ? option.conversations.length : 0, | ||||||
|             promises = []; |             promises = []; | ||||||
|         let data, |         let data: {conversations: AddonMessagesConversationForList[], canLoadMore: boolean}, | ||||||
|             offlineMessages; |             offlineMessages; | ||||||
| 
 | 
 | ||||||
|         // Get the conversations and, if needed, the offline messages. Always try to get the latest data.
 |         // Get the conversations and, if needed, the offline messages. Always try to get the latest data.
 | ||||||
| @ -422,7 +424,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * @param option The option to search in. If not defined, search in all options. |      * @param option The option to search in. If not defined, search in all options. | ||||||
|      * @return Conversation. |      * @return Conversation. | ||||||
|      */ |      */ | ||||||
|     protected findConversation(conversationId: number, userId?: number, option?: any): any { |     protected findConversation(conversationId: number, userId?: number, option?: AddonMessagesGroupConversationOption) | ||||||
|  |             : AddonMessagesConversationForList { | ||||||
|  | 
 | ||||||
|         if (conversationId) { |         if (conversationId) { | ||||||
|             const conversations = option ? (option.conversations || []) : ((this.favourites.conversations || []) |             const conversations = option ? (option.conversations || []) : ((this.favourites.conversations || []) | ||||||
|                     .concat(this.group.conversations || []).concat(this.individual.conversations || [])); |                     .concat(this.group.conversations || []).concat(this.individual.conversations || [])); | ||||||
| @ -445,7 +449,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * |      * | ||||||
|      * @return Option currently expanded. |      * @return Option currently expanded. | ||||||
|      */ |      */ | ||||||
|     protected getExpandedOption(): any { |     protected getExpandedOption(): AddonMessagesGroupConversationOption { | ||||||
|         if (this.favourites.expanded) { |         if (this.favourites.expanded) { | ||||||
|             return this.favourites; |             return this.favourites; | ||||||
|         } else if (this.group.expanded) { |         } else if (this.group.expanded) { | ||||||
| @ -495,9 +499,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * |      * | ||||||
|      * @param option The option to fetch data for. |      * @param option The option to fetch data for. | ||||||
|      * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. |      * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. | ||||||
|      * @return Resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     loadMoreConversations(option: any, infiniteComplete?: any): Promise<any> { |     loadMoreConversations(option: AddonMessagesGroupConversationOption, infiniteComplete?: any): Promise<void> { | ||||||
|         return this.fetchDataForOption(option, true).catch((error) => { |         return this.fetchDataForOption(option, true).catch((error) => { | ||||||
|             this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true); |             this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true); | ||||||
|             option.loadMoreError = true; |             option.loadMoreError = true; | ||||||
| @ -513,7 +517,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * @param messages Offline messages. |      * @param messages Offline messages. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected loadOfflineMessages(option: any, messages: any[]): Promise<any> { |     protected loadOfflineMessages(option: AddonMessagesGroupConversationOption, messages: any[]): Promise<any> { | ||||||
|         const promises = []; |         const promises = []; | ||||||
| 
 | 
 | ||||||
|         messages.forEach((message) => { |         messages.forEach((message) => { | ||||||
| @ -588,7 +592,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * @param conversation Conversation where to put the last message. |      * @param conversation Conversation where to put the last message. | ||||||
|      * @param message Offline message to add. |      * @param message Offline message to add. | ||||||
|      */ |      */ | ||||||
|     protected addLastOfflineMessage(conversation: any, message: any): void { |     protected addLastOfflineMessage(conversation: any, message: AddonMessagesConversationMessage): void { | ||||||
|         conversation.lastmessage = message.text; |         conversation.lastmessage = message.text; | ||||||
|         conversation.lastmessagedate = message.timecreated / 1000; |         conversation.lastmessagedate = message.timecreated / 1000; | ||||||
|         conversation.lastmessagepending = true; |         conversation.lastmessagepending = true; | ||||||
| @ -601,7 +605,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * @param conversation Conversation to check. |      * @param conversation Conversation to check. | ||||||
|      * @return Option object. |      * @return Option object. | ||||||
|      */ |      */ | ||||||
|     protected getConversationOption(conversation: any): any { |     protected getConversationOption(conversation: AddonMessagesConversationForList): AddonMessagesGroupConversationOption { | ||||||
|         if (conversation.isfavourite) { |         if (conversation.isfavourite) { | ||||||
|             return this.favourites; |             return this.favourites; | ||||||
|         } else if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP) { |         } else if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP) { | ||||||
| @ -618,7 +622,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * @param refreshUnreadCounts Whether to refresh unread counts. |      * @param refreshUnreadCounts Whether to refresh unread counts. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise<any> { |     refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise<void> { | ||||||
|         // Don't invalidate conversations and so, they always try to get latest data.
 |         // Don't invalidate conversations and so, they always try to get latest data.
 | ||||||
|         const promises = [ |         const promises = [ | ||||||
|             this.messagesProvider.invalidateContactRequestsCountCache(this.siteId) |             this.messagesProvider.invalidateContactRequestsCountCache(this.siteId) | ||||||
| @ -638,7 +642,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * |      * | ||||||
|      * @param option The option to expand/collapse. |      * @param option The option to expand/collapse. | ||||||
|      */ |      */ | ||||||
|     toggle(option: any): void { |     toggle(option: AddonMessagesGroupConversationOption): void { | ||||||
|         if (option.expanded) { |         if (option.expanded) { | ||||||
|             // Already expanded, close it.
 |             // Already expanded, close it.
 | ||||||
|             option.expanded = false; |             option.expanded = false; | ||||||
| @ -658,7 +662,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|      * @param getCounts Whether to get counts data. |      * @param getCounts Whether to get counts data. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected expandOption(option: any, getCounts?: boolean): Promise<any> { |     protected expandOption(option: AddonMessagesGroupConversationOption, getCounts?: boolean): Promise<void> { | ||||||
|         // Collapse all and expand the right one.
 |         // Collapse all and expand the right one.
 | ||||||
|         this.favourites.expanded = false; |         this.favourites.expanded = false; | ||||||
|         this.group.expanded = false; |         this.group.expanded = false; | ||||||
| @ -715,3 +719,25 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | |||||||
|         this.memberInfoObserver && this.memberInfoObserver.off(); |         this.memberInfoObserver && this.memberInfoObserver.off(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Conversation options. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGroupConversationOption = { | ||||||
|  |     type: number; // Option type.
 | ||||||
|  |     favourites: boolean; // Whether it contains favourites conversations.
 | ||||||
|  |     count: number; // Number of conversations.
 | ||||||
|  |     unread?: number; // Number of unread conversations.
 | ||||||
|  |     expanded?: boolean; // Whether the option is currently expanded.
 | ||||||
|  |     loading?: boolean; // Whether the option is being loaded.
 | ||||||
|  |     canLoadMore?: boolean; // Whether it can load more data.
 | ||||||
|  |     loadMoreError?: boolean; // Whether there was an error loading more conversations.
 | ||||||
|  |     conversations?: AddonMessagesConversationForList[]; // List of conversations.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Formatted conversation with some calculated data for the list. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesConversationForList = AddonMessagesConversationFormatted & { | ||||||
|  |     lastmessagepending?: boolean; // Calculated in the app. Whether last message is pending to be sent.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Component, OnDestroy, ViewChild } from '@angular/core'; | |||||||
| import { IonicPage } from 'ionic-angular'; | import { IonicPage } from 'ionic-angular'; | ||||||
| import { CoreEventsProvider } from '@providers/events'; | import { CoreEventsProvider } from '@providers/events'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { AddonMessagesProvider } from '../../providers/messages'; | import { AddonMessagesProvider, AddonMessagesConversationMember, AddonMessagesMessageAreaContact } from '../../providers/messages'; | ||||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreAppProvider } from '@providers/app'; | import { CoreAppProvider } from '@providers/app'; | ||||||
| @ -38,21 +38,21 @@ export class AddonMessagesSearchPage implements OnDestroy { | |||||||
|     contacts = { |     contacts = { | ||||||
|         type: 'contacts', |         type: 'contacts', | ||||||
|         titleString: 'addon.messages.contacts', |         titleString: 'addon.messages.contacts', | ||||||
|         results: [], |         results: <AddonMessagesConversationMember[]> [], | ||||||
|         canLoadMore: false, |         canLoadMore: false, | ||||||
|         loadingMore: false |         loadingMore: false | ||||||
|     }; |     }; | ||||||
|     nonContacts = { |     nonContacts = { | ||||||
|         type: 'noncontacts', |         type: 'noncontacts', | ||||||
|         titleString: 'addon.messages.noncontacts', |         titleString: 'addon.messages.noncontacts', | ||||||
|         results: [], |         results: <AddonMessagesConversationMember[]> [], | ||||||
|         canLoadMore: false, |         canLoadMore: false, | ||||||
|         loadingMore: false |         loadingMore: false | ||||||
|     }; |     }; | ||||||
|     messages = { |     messages = { | ||||||
|         type: 'messages', |         type: 'messages', | ||||||
|         titleString: 'addon.messages.messages', |         titleString: 'addon.messages.messages', | ||||||
|         results: [], |         results: <AddonMessagesMessageAreaContact[]> [], | ||||||
|         canLoadMore: false, |         canLoadMore: false, | ||||||
|         loadingMore: false, |         loadingMore: false, | ||||||
|         loadMoreError: false |         loadMoreError: false | ||||||
| @ -116,9 +116,9 @@ export class AddonMessagesSearchPage implements OnDestroy { | |||||||
|         this.displaySearching = !loadMore; |         this.displaySearching = !loadMore; | ||||||
| 
 | 
 | ||||||
|         const promises = []; |         const promises = []; | ||||||
|         let newContacts = []; |         let newContacts: AddonMessagesConversationMember[] = []; | ||||||
|         let newNonContacts = []; |         let newNonContacts: AddonMessagesConversationMember[] = []; | ||||||
|         let newMessages = []; |         let newMessages: AddonMessagesMessageAreaContact[] = []; | ||||||
|         let canLoadMoreContacts = false; |         let canLoadMoreContacts = false; | ||||||
|         let canLoadMoreNonContacts = false; |         let canLoadMoreNonContacts = false; | ||||||
|         let canLoadMoreMessages = false; |         let canLoadMoreMessages = false; | ||||||
|  | |||||||
| @ -14,7 +14,10 @@ | |||||||
| 
 | 
 | ||||||
| import { Component, OnDestroy } from '@angular/core'; | import { Component, OnDestroy } from '@angular/core'; | ||||||
| import { IonicPage } from 'ionic-angular'; | import { IonicPage } from 'ionic-angular'; | ||||||
| import { AddonMessagesProvider } from '../../providers/messages'; | import { | ||||||
|  |     AddonMessagesProvider, AddonMessagesMessagePreferences, AddonMessagesMessagePreferencesNotification, | ||||||
|  |     AddonMessagesMessagePreferencesNotificationProcessor | ||||||
|  | } from '../../providers/messages'; | ||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { CoreAppProvider } from '@providers/app'; | import { CoreAppProvider } from '@providers/app'; | ||||||
| import { CoreConfigProvider } from '@providers/config'; | import { CoreConfigProvider } from '@providers/config'; | ||||||
| @ -34,7 +37,7 @@ import { CoreConstants } from '@core/constants'; | |||||||
| export class AddonMessagesSettingsPage implements OnDestroy { | export class AddonMessagesSettingsPage implements OnDestroy { | ||||||
|     protected updateTimeout: any; |     protected updateTimeout: any; | ||||||
| 
 | 
 | ||||||
|     preferences: any; |     preferences: AddonMessagesMessagePreferences; | ||||||
|     preferencesLoaded: boolean; |     preferencesLoaded: boolean; | ||||||
|     contactablePrivacy: number | boolean; |     contactablePrivacy: number | boolean; | ||||||
|     advancedContactable = false; // Whether the site supports "advanced" contactable privacy.
 |     advancedContactable = false; // Whether the site supports "advanced" contactable privacy.
 | ||||||
| @ -78,9 +81,9 @@ export class AddonMessagesSettingsPage implements OnDestroy { | |||||||
|     /** |     /** | ||||||
|      * Fetches preference data. |      * Fetches preference data. | ||||||
|      * |      * | ||||||
|      * @return Resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected fetchPreferences(): Promise<any> { |     protected fetchPreferences(): Promise<void> { | ||||||
|         return this.messagesProvider.getMessagePreferences().then((preferences) => { |         return this.messagesProvider.getMessagePreferences().then((preferences) => { | ||||||
|             if (this.groupMessagingEnabled) { |             if (this.groupMessagingEnabled) { | ||||||
|                 // Simplify the preferences.
 |                 // Simplify the preferences.
 | ||||||
| @ -90,11 +93,12 @@ export class AddonMessagesSettingsPage implements OnDestroy { | |||||||
|                         return notification.preferencekey == AddonMessagesProvider.NOTIFICATION_PREFERENCES_KEY; |                         return notification.preferencekey == AddonMessagesProvider.NOTIFICATION_PREFERENCES_KEY; | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|                     for (const notification of component.notifications) { |                     component.notifications.forEach((notification) => { | ||||||
|                         for (const processor of notification.processors) { |                         notification.processors.forEach( | ||||||
|  |                                 (processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted) => { | ||||||
|                             processor.checked = processor.loggedin.checked || processor.loggedoff.checked; |                             processor.checked = processor.loggedin.checked || processor.loggedoff.checked; | ||||||
|                         } |                         }); | ||||||
|                     } |                     }); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -168,14 +172,16 @@ export class AddonMessagesSettingsPage implements OnDestroy { | |||||||
|      * @param state State name, ['loggedin', 'loggedoff']. |      * @param state State name, ['loggedin', 'loggedoff']. | ||||||
|      * @param processor Notification processor. |      * @param processor Notification processor. | ||||||
|      */ |      */ | ||||||
|     changePreference(notification: any, state: string, processor: any): void { |     changePreference(notification: AddonMessagesMessagePreferencesNotificationFormatted, state: string, | ||||||
|  |             processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted): void { | ||||||
|  | 
 | ||||||
|         if (this.groupMessagingEnabled) { |         if (this.groupMessagingEnabled) { | ||||||
|             // Update both states at the same time.
 |             // Update both states at the same time.
 | ||||||
|             const valueArray = [], |             const valueArray = [], | ||||||
|                 promises = []; |                 promises = []; | ||||||
|             let value = 'none'; |             let value = 'none'; | ||||||
| 
 | 
 | ||||||
|             notification.processors.forEach((processor) => { |             notification.processors.forEach((processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted) => { | ||||||
|                 if (processor.checked) { |                 if (processor.checked) { | ||||||
|                     valueArray.push(processor.name); |                     valueArray.push(processor.name); | ||||||
|                 } |                 } | ||||||
| @ -268,3 +274,17 @@ export class AddonMessagesSettingsPage implements OnDestroy { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Message preferences notification with some caclulated data. | ||||||
|  |  */ | ||||||
|  | type AddonMessagesMessagePreferencesNotificationFormatted = AddonMessagesMessagePreferencesNotification & { | ||||||
|  |     updating?: boolean | {[state: string]: boolean}; // Calculated in the app. Whether the notification is being updated.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Message preferences notification processor with some caclulated data. | ||||||
|  |  */ | ||||||
|  | type AddonMessagesMessagePreferencesNotificationProcessorFormatted = AddonMessagesMessagePreferencesNotificationProcessor & { | ||||||
|  |     checked?: boolean; // Calculated in the app. Whether the processor is checked either for loggedin or loggedoff.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -248,7 +248,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr | |||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     const currentUserId = site.getUserId(), |                     const currentUserId = site.getUserId(), | ||||||
|                         message = conv.messages[0]; // Treat only the last message, is the one we're interested.
 |                         message: any = conv.messages[0]; // Treat only the last message, is the one we're interested.
 | ||||||
| 
 | 
 | ||||||
|                     if (!message || message.useridfrom == currentUserId) { |                     if (!message || message.useridfrom == currentUserId) { | ||||||
|                         // No last message or not from current user. Return empty list.
 |                         // No last message or not from current user. Return empty list.
 | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time'; | |||||||
| import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; | import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; | ||||||
| import { CoreEventsProvider } from '@providers/events'; | import { CoreEventsProvider } from '@providers/events'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
|  | import { CoreWSExternalWarning } from '@providers/ws'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to handle messages. |  * Service to handle messages. | ||||||
| @ -89,9 +90,9 @@ export class AddonMessagesProvider { | |||||||
|      * |      * | ||||||
|      * @param userId User ID of the person to block. |      * @param userId User ID of the person to block. | ||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     blockContact(userId: number, siteId?: string): Promise<any> { |     blockContact(userId: number, siteId?: string): Promise<void> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             let promise; |             let promise; | ||||||
|             if (site.wsAvailable('core_message_block_user')) { |             if (site.wsAvailable('core_message_block_user')) { | ||||||
| @ -313,7 +314,9 @@ export class AddonMessagesProvider { | |||||||
|      * @param userId User ID viewing the conversation. |      * @param userId User ID viewing the conversation. | ||||||
|      * @return Formatted conversation. |      * @return Formatted conversation. | ||||||
|      */ |      */ | ||||||
|     protected formatConversation(conversation: any, userId: number): any { |     protected formatConversation(conversation: AddonMessagesConversationFormatted, userId: number) | ||||||
|  |             : AddonMessagesConversationFormatted { | ||||||
|  | 
 | ||||||
|         const numMessages = conversation.messages.length, |         const numMessages = conversation.messages.length, | ||||||
|             lastMessage = numMessages ? conversation.messages[numMessages - 1] : null; |             lastMessage = numMessages ? conversation.messages[numMessages - 1] : null; | ||||||
| 
 | 
 | ||||||
| @ -536,10 +539,10 @@ export class AddonMessagesProvider { | |||||||
|      * Get all the contacts of the current user. |      * Get all the contacts of the current user. | ||||||
|      * |      * | ||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Resolved with the WS data. |      * @return Promise resolved with the WS data. | ||||||
|      * @deprecated since Moodle 3.6 |      * @deprecated since Moodle 3.6 | ||||||
|      */ |      */ | ||||||
|     getAllContacts(siteId?: string): Promise<any> { |     getAllContacts(siteId?: string): Promise<AddonMessagesGetContactsResult> { | ||||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); |         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
| 
 | 
 | ||||||
|         return this.getContacts(siteId).then((contacts) => { |         return this.getContacts(siteId).then((contacts) => { | ||||||
| @ -562,9 +565,9 @@ export class AddonMessagesProvider { | |||||||
|      * Get all the users blocked by the current user. |      * Get all the users blocked by the current user. | ||||||
|      * |      * | ||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Resolved with the WS data. |      * @return Promise resolved with the WS data. | ||||||
|      */ |      */ | ||||||
|     getBlockedContacts(siteId?: string): Promise<any> { |     getBlockedContacts(siteId?: string): Promise<AddonMessagesGetBlockedUsersResult> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const userId = site.getUserId(), |             const userId = site.getUserId(), | ||||||
|                 params = { |                 params = { | ||||||
| @ -585,19 +588,24 @@ export class AddonMessagesProvider { | |||||||
|      * This excludes the blocked users. |      * This excludes the blocked users. | ||||||
|      * |      * | ||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Resolved with the WS data. |      * @return Promise resolved with the WS data. | ||||||
|      * @deprecated since Moodle 3.6 |      * @deprecated since Moodle 3.6 | ||||||
|      */ |      */ | ||||||
|     getContacts(siteId?: string): Promise<any> { |     getContacts(siteId?: string): Promise<AddonMessagesGetContactsResult> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const preSets = { |             const preSets = { | ||||||
|                 cacheKey: this.getCacheKeyForContacts(), |                 cacheKey: this.getCacheKeyForContacts(), | ||||||
|                 updateFrequency: CoreSite.FREQUENCY_OFTEN |                 updateFrequency: CoreSite.FREQUENCY_OFTEN | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_contacts', undefined, preSets).then((contacts) => { |             return site.read('core_message_get_contacts', undefined, preSets).then((contacts: AddonMessagesGetContactsResult) => { | ||||||
|                 // Filter contacts with negative ID, they are notifications.
 |                 // Filter contacts with negative ID, they are notifications.
 | ||||||
|                 const validContacts = {}; |                 const validContacts: AddonMessagesGetContactsResult = { | ||||||
|  |                     online: [], | ||||||
|  |                     offline: [], | ||||||
|  |                     strangers: [] | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|                 for (const typeName in contacts) { |                 for (const typeName in contacts) { | ||||||
|                     if (!validContacts[typeName]) { |                     if (!validContacts[typeName]) { | ||||||
|                         validContacts[typeName] = []; |                         validContacts[typeName] = []; | ||||||
| @ -621,11 +629,11 @@ export class AddonMessagesProvider { | |||||||
|      * @param limitFrom Position of the first contact to fetch. |      * @param limitFrom Position of the first contact to fetch. | ||||||
|      * @param limitNum Number of contacts to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. |      * @param limitNum Number of contacts to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. | ||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Resolved with the list of user contacts. |      * @return Promise resolved with the list of user contacts. | ||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     getUserContacts(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS , siteId?: string): |     getUserContacts(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS , siteId?: string): | ||||||
|             Promise<{contacts: any[], canLoadMore: boolean}> { |             Promise<{contacts: AddonMessagesConversationMember[], canLoadMore: boolean}> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const params = { |             const params = { | ||||||
| @ -638,7 +646,9 @@ export class AddonMessagesProvider { | |||||||
|                 updateFrequency: CoreSite.FREQUENCY_OFTEN |                 updateFrequency: CoreSite.FREQUENCY_OFTEN | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_user_contacts', params, preSets).then((contacts) => { |             return site.read('core_message_get_user_contacts', params, preSets) | ||||||
|  |                     .then((contacts: AddonMessagesConversationMember[]) => { | ||||||
|  | 
 | ||||||
|                 if (!contacts || !contacts.length) { |                 if (!contacts || !contacts.length) { | ||||||
|                     return { contacts: [], canLoadMore: false }; |                     return { contacts: [], canLoadMore: false }; | ||||||
|                 } |                 } | ||||||
| @ -663,11 +673,11 @@ export class AddonMessagesProvider { | |||||||
|      * @param limitFrom Position of the first contact request to fetch. |      * @param limitFrom Position of the first contact request to fetch. | ||||||
|      * @param limitNum Number of contact requests to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. |      * @param limitNum Number of contact requests to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. | ||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Resolved with the list of contact requests. |      * @return Promise resolved with the list of contact requests. | ||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     getContactRequests(limitFrom: number = 0, limitNum: number =  AddonMessagesProvider.LIMIT_CONTACTS, siteId?: string): |     getContactRequests(limitFrom: number = 0, limitNum: number =  AddonMessagesProvider.LIMIT_CONTACTS, siteId?: string): | ||||||
|             Promise<{requests: any[], canLoadMore: boolean}> { |             Promise<{requests: AddonMessagesConversationMember[], canLoadMore: boolean}> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const data = { |             const data = { | ||||||
| @ -680,7 +690,9 @@ export class AddonMessagesProvider { | |||||||
|                 updateFrequency: CoreSite.FREQUENCY_OFTEN |                 updateFrequency: CoreSite.FREQUENCY_OFTEN | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_contact_requests', data, preSets).then((requests) => { |             return site.read('core_message_get_contact_requests', data, preSets) | ||||||
|  |                     .then((requests: AddonMessagesConversationMember[]) => { | ||||||
|  | 
 | ||||||
|                 if (!requests || !requests.length) { |                 if (!requests || !requests.length) { | ||||||
|                     return { requests: [], canLoadMore: false }; |                     return { requests: [], canLoadMore: false }; | ||||||
|                 } |                 } | ||||||
| @ -716,7 +728,7 @@ export class AddonMessagesProvider { | |||||||
|                 typeExpected: 'number' |                 typeExpected: 'number' | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_received_contact_requests_count', data, preSets).then((count) => { |             return site.read('core_message_get_received_contact_requests_count', data, preSets).then((count: number) => { | ||||||
|                 // Notify the new count so all badges are updated.
 |                 // Notify the new count so all badges are updated.
 | ||||||
|                 this.eventsProvider.trigger(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, { count }, site.id); |                 this.eventsProvider.trigger(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, { count }, site.id); | ||||||
| 
 | 
 | ||||||
| @ -745,7 +757,7 @@ export class AddonMessagesProvider { | |||||||
|      */ |      */ | ||||||
|     getConversation(conversationId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, |     getConversation(conversationId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, | ||||||
|             messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2, |             messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2, | ||||||
|             newestFirst: boolean = true, siteId?: string, userId?: number): Promise<any> { |             newestFirst: boolean = true, siteId?: string, userId?: number): Promise<AddonMessagesConversationFormatted> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| @ -765,7 +777,7 @@ export class AddonMessagesProvider { | |||||||
|                     newestmessagesfirst: newestFirst ? 1 : 0 |                     newestmessagesfirst: newestFirst ? 1 : 0 | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_conversation', params, preSets).then((conversation) => { |             return site.read('core_message_get_conversation', params, preSets).then((conversation: AddonMessagesConversation) => { | ||||||
|                 return this.formatConversation(conversation, userId); |                 return this.formatConversation(conversation, userId); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
| @ -792,7 +804,8 @@ export class AddonMessagesProvider { | |||||||
|      */ |      */ | ||||||
|     getConversationBetweenUsers(otherUserId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, |     getConversationBetweenUsers(otherUserId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, | ||||||
|             messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2, |             messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2, | ||||||
|             newestFirst: boolean = true, siteId?: string, userId?: number, preferCache?: boolean): Promise<any> { |             newestFirst: boolean = true, siteId?: string, userId?: number, preferCache?: boolean) | ||||||
|  |             : Promise<AddonMessagesConversationFormatted> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| @ -813,7 +826,8 @@ export class AddonMessagesProvider { | |||||||
|                     newestmessagesfirst: newestFirst ? 1 : 0 |                     newestmessagesfirst: newestFirst ? 1 : 0 | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_conversation_between_users', params, preSets).then((conversation) => { |             return site.read('core_message_get_conversation_between_users', params, preSets) | ||||||
|  |                     .then((conversation: AddonMessagesConversation) => { | ||||||
|                 return this.formatConversation(conversation, userId); |                 return this.formatConversation(conversation, userId); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
| @ -826,12 +840,11 @@ export class AddonMessagesProvider { | |||||||
|      * @param limitFrom Offset for members list. |      * @param limitFrom Offset for members list. | ||||||
|      * @param limitTo Limit of members. |      * @param limitTo Limit of members. | ||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @param userId User ID. If not defined, current user in the site. |      * @param userId User ID. If not defined, current user in | ||||||
|      * @return Promise resolved with the response. |  | ||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     getConversationMembers(conversationId: number, limitFrom: number = 0, limitTo?: number, includeContactRequests?: boolean, |     getConversationMembers(conversationId: number, limitFrom: number = 0, limitTo?: number, includeContactRequests?: boolean, | ||||||
|             siteId?: string, userId?: number): Promise<any> { |             siteId?: string, userId?: number): Promise<{members: AddonMessagesConversationMember[], canLoadMore: boolean}> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| @ -853,18 +866,21 @@ export class AddonMessagesProvider { | |||||||
|                     includeprivacyinfo: 1, |                     includeprivacyinfo: 1, | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_conversation_members', params, preSets).then((members) => { |             return site.read('core_message_get_conversation_members', params, preSets) | ||||||
|                 const result: any = {}; |                     .then((members: AddonMessagesConversationMember[]) => { | ||||||
| 
 | 
 | ||||||
|                 if (limitTo < 1) { |                 if (limitTo < 1) { | ||||||
|                     result.canLoadMore = false; |                     return { | ||||||
|                     result.members = members; |                         canLoadMore: false, | ||||||
|  |                         members: members | ||||||
|  |                     }; | ||||||
|                 } else { |                 } else { | ||||||
|                     result.canLoadMore = members.length > limitTo; |                     return { | ||||||
|                     result.members = members.slice(0, limitTo); |                         canLoadMore: members.length > limitTo, | ||||||
|  |                         members: members.slice(0, limitTo) | ||||||
|  |                     }; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 return result; |  | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @ -884,7 +900,8 @@ export class AddonMessagesProvider { | |||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     getConversationMessages(conversationId: number, excludePending: boolean, limitFrom: number = 0, limitTo?: number, |     getConversationMessages(conversationId: number, excludePending: boolean, limitFrom: number = 0, limitTo?: number, | ||||||
|             newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number): Promise<any> { |             newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number) | ||||||
|  |             : Promise<AddonMessagesGetConversationMessagesResult> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| @ -913,7 +930,9 @@ export class AddonMessagesProvider { | |||||||
|                 preSets['emergencyCache'] = false; |                 preSets['emergencyCache'] = false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_conversation_messages', params, preSets).then((result) => { |             return site.read('core_message_get_conversation_messages', params, preSets) | ||||||
|  |                     .then((result: AddonMessagesGetConversationMessagesResult) => { | ||||||
|  | 
 | ||||||
|                 if (limitTo < 1) { |                 if (limitTo < 1) { | ||||||
|                     result.canLoadMore = false; |                     result.canLoadMore = false; | ||||||
|                     result.messages = result.messages; |                     result.messages = result.messages; | ||||||
| @ -975,7 +994,8 @@ export class AddonMessagesProvider { | |||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number, |     getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number, | ||||||
|             forceCache?: boolean, ignoreCache?: boolean): Promise<{conversations: any[], canLoadMore: boolean}> { |             forceCache?: boolean, ignoreCache?: boolean) | ||||||
|  |             : Promise<{conversations: AddonMessagesConversationFormatted[], canLoadMore: boolean}> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| @ -1017,7 +1037,7 @@ export class AddonMessagesProvider { | |||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 return Promise.reject(error); |                 return Promise.reject(error); | ||||||
|             }).then((response) => { |             }).then((response: AddonMessagesGetConversationsResult) => { | ||||||
|                 // Format the conversations, adding some calculated fields.
 |                 // Format the conversations, adding some calculated fields.
 | ||||||
|                 const conversations = response.conversations.slice(0, this.LIMIT_MESSAGES).map((conversation) => { |                 const conversations = response.conversations.slice(0, this.LIMIT_MESSAGES).map((conversation) => { | ||||||
|                         return this.formatConversation(conversation, userId); |                         return this.formatConversation(conversation, userId); | ||||||
| @ -1053,7 +1073,9 @@ export class AddonMessagesProvider { | |||||||
|                 cacheKey: this.getCacheKeyForConversationCounts() |                 cacheKey: this.getCacheKeyForConversationCounts() | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_conversation_counts', {}, preSets).then((result) => { |             return site.read('core_message_get_conversation_counts', {}, preSets) | ||||||
|  |                     .then((result: AddonMessagesGetConversationCountsResult) => { | ||||||
|  | 
 | ||||||
|                 const counts = { |                 const counts = { | ||||||
|                     favourites: result.favourites, |                     favourites: result.favourites, | ||||||
|                     individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], |                     individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], | ||||||
| @ -1080,10 +1102,14 @@ export class AddonMessagesProvider { | |||||||
|      * @return Promise resolved with messages and a boolean telling if can load more messages. |      * @return Promise resolved with messages and a boolean telling if can load more messages. | ||||||
|      */ |      */ | ||||||
|     getDiscussion(userId: number, excludePending: boolean, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, |     getDiscussion(userId: number, excludePending: boolean, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, | ||||||
|             lfSentUnread: number = 0, lfSentRead: number = 0, toDisplay: boolean = true, siteId?: string): Promise<any> { |             lfSentUnread: number = 0, lfSentRead: number = 0, toDisplay: boolean = true, siteId?: string) | ||||||
|  |             : Promise<{messages: AddonMessagesGetMessagesMessage[], canLoadMore: boolean}> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const result = {}, |             const result = { | ||||||
|  |                     messages: <AddonMessagesGetMessagesMessage[]> [], | ||||||
|  |                     canLoadMore: false | ||||||
|  |                 }, | ||||||
|                 preSets = { |                 preSets = { | ||||||
|                     cacheKey: this.getCacheKeyForDiscussion(userId) |                     cacheKey: this.getCacheKeyForDiscussion(userId) | ||||||
|                 }, |                 }, | ||||||
| @ -1107,7 +1133,7 @@ export class AddonMessagesProvider { | |||||||
|             // Get message received by current user.
 |             // Get message received by current user.
 | ||||||
|             return this.getRecentMessages(params, preSets, lfReceivedUnread, lfReceivedRead, toDisplay, site.getId()) |             return this.getRecentMessages(params, preSets, lfReceivedUnread, lfReceivedRead, toDisplay, site.getId()) | ||||||
|                     .then((response) => { |                     .then((response) => { | ||||||
|                 result['messages'] = response; |                 result.messages = response; | ||||||
|                 params.useridto = userId; |                 params.useridto = userId; | ||||||
|                 params.useridfrom = site.getUserId(); |                 params.useridfrom = site.getUserId(); | ||||||
|                 hasReceived = response.length > 0; |                 hasReceived = response.length > 0; | ||||||
| @ -1115,16 +1141,16 @@ export class AddonMessagesProvider { | |||||||
|                 // Get message sent by current user.
 |                 // Get message sent by current user.
 | ||||||
|                 return this.getRecentMessages(params, preSets, lfSentUnread, lfSentRead, toDisplay, siteId); |                 return this.getRecentMessages(params, preSets, lfSentUnread, lfSentRead, toDisplay, siteId); | ||||||
|             }).then((response) => { |             }).then((response) => { | ||||||
|                 result['messages'] = result['messages'].concat(response); |                 result.messages = result.messages.concat(response); | ||||||
|                 hasSent = response.length > 0; |                 hasSent = response.length > 0; | ||||||
| 
 | 
 | ||||||
|                 if (result['messages'].length > this.LIMIT_MESSAGES) { |                 if (result.messages.length > this.LIMIT_MESSAGES) { | ||||||
|                     // Sort messages and get the more recent ones.
 |                     // Sort messages and get the more recent ones.
 | ||||||
|                     result['canLoadMore'] = true; |                     result.canLoadMore = true; | ||||||
|                     result['messages'] = this.sortMessages(result['messages']); |                     result.messages = this.sortMessages(result['messages']); | ||||||
|                     result['messages'] = result['messages'].slice(-this.LIMIT_MESSAGES); |                     result.messages = result.messages.slice(-this.LIMIT_MESSAGES); | ||||||
|                 } else { |                 } else { | ||||||
|                     result['canLoadMore'] = result['messages'].length == this.LIMIT_MESSAGES && (!hasReceived || !hasSent); |                     result.canLoadMore = result.messages.length == this.LIMIT_MESSAGES && (!hasReceived || !hasSent); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if (excludePending) { |                 if (excludePending) { | ||||||
| @ -1140,7 +1166,7 @@ export class AddonMessagesProvider { | |||||||
|                         message.text = message.smallmessage; |                         message.text = message.smallmessage; | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|                     result['messages'] = result['messages'].concat(offlineMessages); |                     result.messages = result.messages.concat(offlineMessages); | ||||||
| 
 | 
 | ||||||
|                     return result; |                     return result; | ||||||
|                 }); |                 }); | ||||||
| @ -1153,11 +1179,11 @@ export class AddonMessagesProvider { | |||||||
|      * If the site is 3.6 or higher, please use getConversations. |      * If the site is 3.6 or higher, please use getConversations. | ||||||
|      * |      * | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Resolved with an object where the keys are the user ID of the other user. |      * @return Promise resolved with an object where the keys are the user ID of the other user. | ||||||
|      */ |      */ | ||||||
|     getDiscussions(siteId?: string): Promise<any> { |     getDiscussions(siteId?: string): Promise<{[userId: number]: AddonMessagesDiscussion}> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const discussions = {}, |             const discussions: {[userId: number]: AddonMessagesDiscussion} = {}, | ||||||
|                 currentUserId = site.getUserId(), |                 currentUserId = site.getUserId(), | ||||||
|                 params = { |                 params = { | ||||||
|                     useridto: currentUserId, |                     useridto: currentUserId, | ||||||
| @ -1171,7 +1197,7 @@ export class AddonMessagesProvider { | |||||||
|             /** |             /** | ||||||
|              * Convenience function to treat a recent message, adding it to discussions list if needed. |              * Convenience function to treat a recent message, adding it to discussions list if needed. | ||||||
|              */ |              */ | ||||||
|             const treatRecentMessage = (message: any, userId: number, userFullname: string): void => { |             const treatRecentMessage = (message: AddonMessagesGetMessagesMessage, userId: number, userFullname: string): void => { | ||||||
|                 if (typeof discussions[userId] === 'undefined') { |                 if (typeof discussions[userId] === 'undefined') { | ||||||
|                     discussions[userId] = { |                     discussions[userId] = { | ||||||
|                         fullname: userFullname, |                         fullname: userFullname, | ||||||
| @ -1272,7 +1298,7 @@ export class AddonMessagesProvider { | |||||||
|      * @return Promise resolved with the member info. |      * @return Promise resolved with the member info. | ||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise<any> { |     getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise<AddonMessagesConversationMember> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| 
 | 
 | ||||||
| @ -1287,7 +1313,9 @@ export class AddonMessagesProvider { | |||||||
|                     includeprivacyinfo: 1, |                     includeprivacyinfo: 1, | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_member_info', params, preSets).then((members) => { |             return site.read('core_message_get_member_info', params, preSets) | ||||||
|  |                     .then((members: AddonMessagesConversationMember[]): any => { | ||||||
|  | 
 | ||||||
|                 if (!members || members.length < 1) { |                 if (!members || members.length < 1) { | ||||||
|                     // Should never happen.
 |                     // Should never happen.
 | ||||||
|                     return Promise.reject(null); |                     return Promise.reject(null); | ||||||
| @ -1313,7 +1341,7 @@ export class AddonMessagesProvider { | |||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Promise resolved with the message preferences. |      * @return Promise resolved with the message preferences. | ||||||
|      */ |      */ | ||||||
|     getMessagePreferences(siteId?: string): Promise<any> { |     getMessagePreferences(siteId?: string): Promise<AddonMessagesMessagePreferences> { | ||||||
|         this.logger.debug('Get message preferences'); |         this.logger.debug('Get message preferences'); | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| @ -1322,7 +1350,9 @@ export class AddonMessagesProvider { | |||||||
|                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES |                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_user_message_preferences', {}, preSets).then((data) => { |             return site.read('core_message_get_user_message_preferences', {}, preSets) | ||||||
|  |                     .then((data: AddonMessagesGetUserMessagePreferencesResult): any => { | ||||||
|  | 
 | ||||||
|                 if (data.preferences) { |                 if (data.preferences) { | ||||||
|                     data.preferences.blocknoncontacts = data.blocknoncontacts; |                     data.preferences.blocknoncontacts = data.blocknoncontacts; | ||||||
| 
 | 
 | ||||||
| @ -1341,15 +1371,18 @@ export class AddonMessagesProvider { | |||||||
|      * @param preSets Set of presets for the WS. |      * @param preSets Set of presets for the WS. | ||||||
|      * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. |      * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. | ||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|  |      * @return Promise resolved with the data. | ||||||
|      */ |      */ | ||||||
|     protected getMessages(params: any, preSets: any, toDisplay: boolean = true, siteId?: string): Promise<any> { |     protected getMessages(params: any, preSets: any, toDisplay: boolean = true, siteId?: string) | ||||||
|  |             : Promise<AddonMessagesGetMessagesResult> { | ||||||
|  | 
 | ||||||
|         params['type'] = 'conversations'; |         params['type'] = 'conversations'; | ||||||
|         params['newestfirst'] = 1; |         params['newestfirst'] = 1; | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const userId = site.getUserId(); |             const userId = site.getUserId(); | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_messages', params, preSets).then((response) => { |             return site.read('core_message_get_messages', params, preSets).then((response: AddonMessagesGetMessagesResult) => { | ||||||
|                 response.messages.forEach((message) => { |                 response.messages.forEach((message) => { | ||||||
|                     message.read = params.read == 0 ? 0 : 1; |                     message.read = params.read == 0 ? 0 : 1; | ||||||
|                     // Convert times to milliseconds.
 |                     // Convert times to milliseconds.
 | ||||||
| @ -1377,9 +1410,10 @@ export class AddonMessagesProvider { | |||||||
|      * @param limitFromRead Number of unread messages already fetched, so fetch will be done from this number. |      * @param limitFromRead Number of unread messages already fetched, so fetch will be done from this number. | ||||||
|      * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. |      * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. | ||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|  |      * @return Promise resolved with the data. | ||||||
|      */ |      */ | ||||||
|     protected getRecentMessages(params: any, preSets: any, limitFromUnread: number = 0, limitFromRead: number = 0, |     protected getRecentMessages(params: any, preSets: any, limitFromUnread: number = 0, limitFromRead: number = 0, | ||||||
|             toDisplay: boolean = true, siteId?: string): Promise<any> { |             toDisplay: boolean = true, siteId?: string): Promise<AddonMessagesGetMessagesMessage[]> { | ||||||
|         limitFromUnread = limitFromUnread || 0; |         limitFromUnread = limitFromUnread || 0; | ||||||
|         limitFromRead = limitFromRead || 0; |         limitFromRead = limitFromRead || 0; | ||||||
| 
 | 
 | ||||||
| @ -1427,7 +1461,7 @@ export class AddonMessagesProvider { | |||||||
|      * @since 3.7 |      * @since 3.7 | ||||||
|      */ |      */ | ||||||
|     getSelfConversation(messageOffset: number = 0, messageLimit: number = 1, newestFirst: boolean = true, siteId?: string, |     getSelfConversation(messageOffset: number = 0, messageLimit: number = 1, newestFirst: boolean = true, siteId?: string, | ||||||
|             userId?: number): Promise<any> { |             userId?: number): Promise<AddonMessagesConversationFormatted> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| @ -1442,7 +1476,8 @@ export class AddonMessagesProvider { | |||||||
|                     newestmessagesfirst: newestFirst ? 1 : 0 |                     newestmessagesfirst: newestFirst ? 1 : 0 | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_self_conversation', params, preSets).then((conversation) => { |             return site.read('core_message_get_self_conversation', params, preSets) | ||||||
|  |                     .then((conversation: AddonMessagesConversation) => { | ||||||
|                 return this.formatConversation(conversation, userId); |                 return this.formatConversation(conversation, userId); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
| @ -1466,7 +1501,8 @@ export class AddonMessagesProvider { | |||||||
|                     cacheKey: this.getCacheKeyForUnreadConversationCounts() |                     cacheKey: this.getCacheKeyForUnreadConversationCounts() | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|                 promise = site.read('core_message_get_unread_conversation_counts', {}, preSets).then((result) => { |                 promise = site.read('core_message_get_unread_conversation_counts', {}, preSets) | ||||||
|  |                         .then((result: AddonMessagesGetUnreadConversationCountsResult) => { | ||||||
|                     return { |                     return { | ||||||
|                         favourites: result.favourites, |                         favourites: result.favourites, | ||||||
|                         individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], |                         individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], | ||||||
| @ -1485,7 +1521,7 @@ export class AddonMessagesProvider { | |||||||
|                         typeExpected: 'number' |                         typeExpected: 'number' | ||||||
|                     }; |                     }; | ||||||
| 
 | 
 | ||||||
|                 promise = site.read('core_message_get_unread_conversations_count', params, preSets).then((count) => { |                 promise = site.read('core_message_get_unread_conversations_count', params, preSets).then((count: number) => { | ||||||
|                     return { favourites: 0, individual: count, group: 0, self: 0 }; |                     return { favourites: 0, individual: count, group: 0, self: 0 }; | ||||||
|                 }); |                 }); | ||||||
|             } else { |             } else { | ||||||
| @ -1536,7 +1572,7 @@ export class AddonMessagesProvider { | |||||||
|      * @return Promise resolved with the message unread count. |      * @return Promise resolved with the message unread count. | ||||||
|      */ |      */ | ||||||
|     getUnreadReceivedMessages(toDisplay: boolean = true, forceCache: boolean = false, ignoreCache: boolean = false, |     getUnreadReceivedMessages(toDisplay: boolean = true, forceCache: boolean = false, ignoreCache: boolean = false, | ||||||
|             siteId?: string): Promise<any> { |             siteId?: string): Promise<AddonMessagesGetMessagesResult> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const params = { |             const params = { | ||||||
|                     read: 0, |                     read: 0, | ||||||
| @ -2049,7 +2085,7 @@ export class AddonMessagesProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved with boolean marking success or not. |      * @return Promise resolved with boolean marking success or not. | ||||||
|      */ |      */ | ||||||
|     markMessageRead(messageId: number, siteId?: string): Promise<any> { |     markMessageRead(messageId: number, siteId?: string): Promise<AddonMessagesMarkMessageReadResult> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const params = { |             const params = { | ||||||
|                 messageid: messageId, |                 messageid: messageId, | ||||||
| @ -2067,7 +2103,7 @@ export class AddonMessagesProvider { | |||||||
|      * @return Promise resolved if success. |      * @return Promise resolved if success. | ||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     markAllConversationMessagesRead(conversationId?: number): Promise<any> { |     markAllConversationMessagesRead(conversationId?: number): Promise<null> { | ||||||
|         const params = { |         const params = { | ||||||
|                 userid: this.sitesProvider.getCurrentSiteUserId(), |                 userid: this.sitesProvider.getCurrentSiteUserId(), | ||||||
|                 conversationid: conversationId |                 conversationid: conversationId | ||||||
| @ -2085,7 +2121,7 @@ export class AddonMessagesProvider { | |||||||
|      * @param userIdFrom User Id for the sender. |      * @param userIdFrom User Id for the sender. | ||||||
|      * @return Promise resolved with boolean marking success or not. |      * @return Promise resolved with boolean marking success or not. | ||||||
|      */ |      */ | ||||||
|     markAllMessagesRead(userIdFrom?: number): Promise<any> { |     markAllMessagesRead(userIdFrom?: number): Promise<boolean> { | ||||||
|         const params = { |         const params = { | ||||||
|                 useridto: this.sitesProvider.getCurrentSiteUserId(), |                 useridto: this.sitesProvider.getCurrentSiteUserId(), | ||||||
|                 useridfrom: userIdFrom |                 useridfrom: userIdFrom | ||||||
| @ -2217,8 +2253,9 @@ export class AddonMessagesProvider { | |||||||
|      * @param query The query string. |      * @param query The query string. | ||||||
|      * @param limit The number of results to return, 0 for none. |      * @param limit The number of results to return, 0 for none. | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|  |      * @return Promise resolved with the contacts. | ||||||
|      */ |      */ | ||||||
|     searchContacts(query: string, limit: number = 100, siteId?: string): Promise<any> { |     searchContacts(query: string, limit: number = 100, siteId?: string): Promise<AddonMessagesSearchContactsContact[]> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const data = { |             const data = { | ||||||
|                     searchtext: query, |                     searchtext: query, | ||||||
| @ -2228,7 +2265,9 @@ export class AddonMessagesProvider { | |||||||
|                     getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
 |                     getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
 | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_search_contacts', data, preSets).then((contacts) => { |             return site.read('core_message_search_contacts', data, preSets) | ||||||
|  |                     .then((contacts: AddonMessagesSearchContactsContact[]) => { | ||||||
|  | 
 | ||||||
|                 if (limit && contacts.length > limit) { |                 if (limit && contacts.length > limit) { | ||||||
|                     contacts = contacts.splice(0, limit); |                     contacts = contacts.splice(0, limit); | ||||||
|                 } |                 } | ||||||
| @ -2250,7 +2289,7 @@ export class AddonMessagesProvider { | |||||||
|      * @return Promise resolved with the results. |      * @return Promise resolved with the results. | ||||||
|      */ |      */ | ||||||
|     searchMessages(query: string, userId?: number, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, |     searchMessages(query: string, userId?: number, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, | ||||||
|             siteId?: string): Promise<{messages: any[], canLoadMore: boolean}> { |             siteId?: string): Promise<{messages: AddonMessagesMessageAreaContact[], canLoadMore: boolean}> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const params = { |             const params = { | ||||||
| @ -2263,13 +2302,15 @@ export class AddonMessagesProvider { | |||||||
|                     getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
 |                     getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
 | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_data_for_messagearea_search_messages', params, preSets).then((result) => { |             return site.read('core_message_data_for_messagearea_search_messages', params, preSets) | ||||||
|  |                     .then((result: AddonMessagesDataForMessageAreaSearchMessagesResult) => { | ||||||
|  | 
 | ||||||
|                 if (!result.contacts || !result.contacts.length) { |                 if (!result.contacts || !result.contacts.length) { | ||||||
|                     return { messages: [], canLoadMore: false }; |                     return { messages: [], canLoadMore: false }; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 result.contacts.forEach((result) => { |                 result.contacts.forEach((contact) => { | ||||||
|                     result.id = result.userid; |                     contact.id = contact.userid; | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|                 this.userProvider.storeUsers(result.contacts, site.id); |                 this.userProvider.storeUsers(result.contacts, site.id); | ||||||
| @ -2297,7 +2338,8 @@ export class AddonMessagesProvider { | |||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     searchUsers(query: string, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, siteId?: string): |     searchUsers(query: string, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, siteId?: string): | ||||||
|             Promise<{contacts: any[], nonContacts: any[], canLoadMoreContacts: boolean, canLoadMoreNonContacts: boolean}> { |             Promise<{contacts: AddonMessagesConversationMember[], nonContacts: AddonMessagesConversationMember[], | ||||||
|  |                 canLoadMoreContacts: boolean, canLoadMoreNonContacts: boolean}> { | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const data = { |             const data = { | ||||||
| @ -2310,7 +2352,7 @@ export class AddonMessagesProvider { | |||||||
|                     getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
 |                     getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
 | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_message_search_users', data, preSets).then((result) => { |             return site.read('core_message_message_search_users', data, preSets).then((result: AddonMessagesSearchUsersResult) => { | ||||||
|                 const contacts = result.contacts || []; |                 const contacts = result.contacts || []; | ||||||
|                 const nonContacts = result.noncontacts || []; |                 const nonContacts = result.noncontacts || []; | ||||||
| 
 | 
 | ||||||
| @ -2341,7 +2383,9 @@ export class AddonMessagesProvider { | |||||||
|      *         - sent (Boolean) True if message was sent to server, false if stored in device. |      *         - sent (Boolean) True if message was sent to server, false if stored in device. | ||||||
|      *         - message (Object) If sent=false, contains the stored message. |      *         - message (Object) If sent=false, contains the stored message. | ||||||
|      */ |      */ | ||||||
|     sendMessage(toUserId: number, message: string, siteId?: string): Promise<any> { |     sendMessage(toUserId: number, message: string, siteId?: string) | ||||||
|  |             : Promise<{sent: boolean, message: AddonMessagesSendInstantMessagesMessage}> { | ||||||
|  | 
 | ||||||
|         // Convenience function to store a message to be synchronized later.
 |         // Convenience function to store a message to be synchronized later.
 | ||||||
|         const storeOffline = (): Promise<any> => { |         const storeOffline = (): Promise<any> => { | ||||||
|             return this.messagesOffline.saveMessage(toUserId, message, siteId).then((entry) => { |             return this.messagesOffline.saveMessage(toUserId, message, siteId).then((entry) => { | ||||||
| @ -2395,7 +2439,7 @@ export class AddonMessagesProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved if success, rejected if failure. |      * @return Promise resolved if success, rejected if failure. | ||||||
|      */ |      */ | ||||||
|     sendMessageOnline(toUserId: number, message: string, siteId?: string): Promise<any> { |     sendMessageOnline(toUserId: number, message: string, siteId?: string): Promise<AddonMessagesSendInstantMessagesMessage> { | ||||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); |         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
| 
 | 
 | ||||||
|         const messages = [ |         const messages = [ | ||||||
| @ -2430,7 +2474,7 @@ export class AddonMessagesProvider { | |||||||
|      * @return Promise resolved if success, rejected if failure. Promise resolved doesn't mean that messages |      * @return Promise resolved if success, rejected if failure. Promise resolved doesn't mean that messages | ||||||
|      *         have been sent, the resolve param can contain errors for messages not sent. |      *         have been sent, the resolve param can contain errors for messages not sent. | ||||||
|      */ |      */ | ||||||
|     sendMessagesOnline(messages: any, siteId?: string): Promise<any> { |     sendMessagesOnline(messages: any[], siteId?: string): Promise<AddonMessagesSendInstantMessagesMessage[]> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const data = { |             const data = { | ||||||
|                 messages: messages |                 messages: messages | ||||||
| @ -2451,7 +2495,9 @@ export class AddonMessagesProvider { | |||||||
|      *         - message (any) If sent=false, contains the stored message. |      *         - message (any) If sent=false, contains the stored message. | ||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     sendMessageToConversation(conversation: any, message: string, siteId?: string): Promise<any> { |     sendMessageToConversation(conversation: any, message: string, siteId?: string) | ||||||
|  |             : Promise<{sent: boolean, message: AddonMessagesSendMessagesToConversationMessage}> { | ||||||
|  | 
 | ||||||
|         // Convenience function to store a message to be synchronized later.
 |         // Convenience function to store a message to be synchronized later.
 | ||||||
|         const storeOffline = (): Promise<any> => { |         const storeOffline = (): Promise<any> => { | ||||||
|             return this.messagesOffline.saveConversationMessage(conversation, message, siteId).then((entry) => { |             return this.messagesOffline.saveConversationMessage(conversation, message, siteId).then((entry) => { | ||||||
| @ -2506,7 +2552,8 @@ export class AddonMessagesProvider { | |||||||
|      * @return Promise resolved if success, rejected if failure. |      * @return Promise resolved if success, rejected if failure. | ||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string): Promise<any> { |     sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string) | ||||||
|  |             : Promise<AddonMessagesSendMessagesToConversationMessage> { | ||||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); |         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
| 
 | 
 | ||||||
|         const messages = [ |         const messages = [ | ||||||
| @ -2534,7 +2581,9 @@ export class AddonMessagesProvider { | |||||||
|      * @return Promise resolved if success, rejected if failure. |      * @return Promise resolved if success, rejected if failure. | ||||||
|      * @since 3.6 |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     sendMessagesToConversationOnline(conversationId: number, messages: any, siteId?: string): Promise<any> { |     sendMessagesToConversationOnline(conversationId: number, messages: any[], siteId?: string) | ||||||
|  |             : Promise<AddonMessagesSendMessagesToConversationMessage[]> { | ||||||
|  | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const params = { |             const params = { | ||||||
|                 conversationid: conversationId, |                 conversationid: conversationId, | ||||||
| @ -2603,10 +2652,10 @@ export class AddonMessagesProvider { | |||||||
|      * @param conversations Array of conversations. |      * @param conversations Array of conversations. | ||||||
|      * @return Conversations sorted with most recent last. |      * @return Conversations sorted with most recent last. | ||||||
|      */ |      */ | ||||||
|     sortConversations(conversations: any[]): any[] { |     sortConversations(conversations: AddonMessagesConversationFormatted[]): AddonMessagesConversationFormatted[] { | ||||||
|         return conversations.sort((a, b) => { |         return conversations.sort((a, b) => { | ||||||
|             const timeA = parseInt(a.lastmessagedate, 10), |             const timeA = Number(a.lastmessagedate), | ||||||
|                 timeB = parseInt(b.lastmessagedate, 10); |                 timeB = Number(b.lastmessagedate); | ||||||
| 
 | 
 | ||||||
|             if (timeA == timeB && a.id) { |             if (timeA == timeB && a.id) { | ||||||
|                 // Same time, sort by ID.
 |                 // Same time, sort by ID.
 | ||||||
| @ -2651,7 +2700,9 @@ export class AddonMessagesProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, message: any, siteId?: string): Promise<any> { |     protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, | ||||||
|  |             message: AddonMessagesGetMessagesMessage | AddonMessagesConversationMessage, siteId?: string): Promise<any> { | ||||||
|  | 
 | ||||||
|         const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT; |         const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT; | ||||||
| 
 | 
 | ||||||
|         // Get the last received message.
 |         // Get the last received message.
 | ||||||
| @ -2675,7 +2726,7 @@ export class AddonMessagesProvider { | |||||||
|      * |      * | ||||||
|      * @param contactTypes List of contacts grouped in types. |      * @param contactTypes List of contacts grouped in types. | ||||||
|      */ |      */ | ||||||
|     protected storeUsersFromAllContacts(contactTypes: any): void { |     protected storeUsersFromAllContacts(contactTypes: AddonMessagesGetContactsResult): void { | ||||||
|         for (const x in contactTypes) { |         for (const x in contactTypes) { | ||||||
|             this.userProvider.storeUsers(contactTypes[x]); |             this.userProvider.storeUsers(contactTypes[x]); | ||||||
|         } |         } | ||||||
| @ -2735,3 +2786,377 @@ export class AddonMessagesProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Conversation. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesConversation = { | ||||||
|  |     id: number; // The conversation id.
 | ||||||
|  |     name: string; // The conversation name, if set.
 | ||||||
|  |     subname: string; // A subtitle for the conversation name, if set.
 | ||||||
|  |     imageurl: string; // A link to the conversation picture, if set.
 | ||||||
|  |     type: number; // The type of the conversation (1=individual,2=group,3=self).
 | ||||||
|  |     membercount: number; // Total number of conversation members.
 | ||||||
|  |     ismuted: boolean; // If the user muted this conversation.
 | ||||||
|  |     isfavourite: boolean; // If the user marked this conversation as a favourite.
 | ||||||
|  |     isread: boolean; // If the user has read all messages in the conversation.
 | ||||||
|  |     unreadcount: number; // The number of unread messages in this conversation.
 | ||||||
|  |     members: AddonMessagesConversationMember[]; | ||||||
|  |     messages: AddonMessagesConversationMessage[]; | ||||||
|  |     candeletemessagesforallusers: boolean; // @since 3.7. If the user can delete messages in the conversation for all users.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Conversation with some calculated data. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesConversationFormatted = AddonMessagesConversation & { | ||||||
|  |     lastmessage?: string; // Calculated in the app. Last message.
 | ||||||
|  |     lastmessagedate?: number; // Calculated in the app. Date the last message was sent.
 | ||||||
|  |     sentfromcurrentuser?: boolean; // Calculated in the app. Whether last message was sent by the current user.
 | ||||||
|  |     name?: string; // Calculated in the app. If private conversation, name of the other user.
 | ||||||
|  |     userid?: number; // Calculated in the app. URL. If private conversation, ID of the other user.
 | ||||||
|  |     showonlinestatus?: boolean; // Calculated in the app. If private conversation, whether to show online status of the other user.
 | ||||||
|  |     isonline?: boolean; // Calculated in the app. If private conversation, whether the other user is online.
 | ||||||
|  |     isblocked?: boolean; // Calculated in the app. If private conversation, whether the other user is blocked.
 | ||||||
|  |     otherUser?: AddonMessagesConversationMember; // Calculated in the app. Other user in the conversation.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Conversation member. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesConversationMember = { | ||||||
|  |     id: number; // The user id.
 | ||||||
|  |     fullname: string; // The user's name.
 | ||||||
|  |     profileurl: string; // The link to the user's profile page.
 | ||||||
|  |     profileimageurl: string; // User picture URL.
 | ||||||
|  |     profileimageurlsmall: string; // Small user picture URL.
 | ||||||
|  |     isonline: boolean; // The user's online status.
 | ||||||
|  |     showonlinestatus: boolean; // Show the user's online status?.
 | ||||||
|  |     isblocked: boolean; // If the user has been blocked.
 | ||||||
|  |     iscontact: boolean; // Is the user a contact?.
 | ||||||
|  |     isdeleted: boolean; // Is the user deleted?.
 | ||||||
|  |     canmessageevenifblocked: boolean; // If the user can still message even if they get blocked.
 | ||||||
|  |     canmessage: boolean; // If the user can be messaged.
 | ||||||
|  |     requirescontact: boolean; // If the user requires to be contacts.
 | ||||||
|  |     contactrequests?: { // The contact requests.
 | ||||||
|  |         id: number; // The id of the contact request.
 | ||||||
|  |         userid: number; // The id of the user who created the contact request.
 | ||||||
|  |         requesteduserid: number; // The id of the user confirming the request.
 | ||||||
|  |         timecreated: number; // The timecreated timestamp for the contact request.
 | ||||||
|  |     }[]; | ||||||
|  |     conversations?: { // Conversations between users.
 | ||||||
|  |         id: number; // Conversations id.
 | ||||||
|  |         type: number; // Conversation type: private or public.
 | ||||||
|  |         name: string; // Multilang compatible conversation name2.
 | ||||||
|  |         timecreated: number; // The timecreated timestamp for the conversation.
 | ||||||
|  |     }[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Conversation message. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesConversationMessage = { | ||||||
|  |     id: number; // The id of the message.
 | ||||||
|  |     useridfrom: number; // The id of the user who sent the message.
 | ||||||
|  |     text: string; // The text of the message.
 | ||||||
|  |     timecreated: number; // The timecreated timestamp for the message.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Message preferences. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesMessagePreferences = { | ||||||
|  |     userid: number; // User id.
 | ||||||
|  |     disableall: number; // Whether all the preferences are disabled.
 | ||||||
|  |     processors: { // Config form values.
 | ||||||
|  |         displayname: string; // Display name.
 | ||||||
|  |         name: string; // Processor name.
 | ||||||
|  |         hassettings: boolean; // Whether has settings.
 | ||||||
|  |         contextid: number; // Context id.
 | ||||||
|  |         userconfigured: number; // Whether is configured by the user.
 | ||||||
|  |     }[]; | ||||||
|  |     components: { // Available components.
 | ||||||
|  |         displayname: string; // Display name.
 | ||||||
|  |         notifications: AddonMessagesMessagePreferencesNotification[]; // List of notificaitons for the component.
 | ||||||
|  |     }[]; | ||||||
|  | } & AddonMessagesMessagePreferencesCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Notification processor in message preferences. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesMessagePreferencesNotification = { | ||||||
|  |     displayname: string; // Display name.
 | ||||||
|  |     preferencekey: string; // Preference key.
 | ||||||
|  |     processors: AddonMessagesMessagePreferencesNotificationProcessor[]; // Processors values for this notification.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Notification processor in message preferences. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesMessagePreferencesNotificationProcessor = { | ||||||
|  |     displayname: string; // Display name.
 | ||||||
|  |     name: string; // Processor name.
 | ||||||
|  |     locked: boolean; // Is locked by admin?.
 | ||||||
|  |     lockedmessage?: string; // Text to display if locked.
 | ||||||
|  |     userconfigured: number; // Is configured?.
 | ||||||
|  |     loggedin: { | ||||||
|  |         name: string; // Name.
 | ||||||
|  |         displayname: string; // Display name.
 | ||||||
|  |         checked: boolean; // Is checked?.
 | ||||||
|  |     }; | ||||||
|  |     loggedoff: { | ||||||
|  |         name: string; // Name.
 | ||||||
|  |         displayname: string; // Display name.
 | ||||||
|  |         checked: boolean; // Is checked?.
 | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Message discussion (before 3.6). | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesDiscussion = { | ||||||
|  |     fullname: string; // Full name of the other user in the discussion.
 | ||||||
|  |     profileimageurl: string; // Profile image of the other user in the discussion.
 | ||||||
|  |     message?: { // Last message.
 | ||||||
|  |         id: number; // Message ID.
 | ||||||
|  |         user: number; // User ID that sent the message.
 | ||||||
|  |         message: string; // Text of the message.
 | ||||||
|  |         timecreated: number; // Time the message was sent.
 | ||||||
|  |         pending?: boolean; // Whether the message is pending to be sent.
 | ||||||
|  |     }; | ||||||
|  |     unread?: boolean; // Whether the discussion has unread messages.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Contact for message area. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesMessageAreaContact = { | ||||||
|  |     userid: number; // The user's id.
 | ||||||
|  |     fullname: string; // The user's name.
 | ||||||
|  |     profileimageurl: string; // User picture URL.
 | ||||||
|  |     profileimageurlsmall: string; // Small user picture URL.
 | ||||||
|  |     ismessaging: boolean; // If we are messaging the user.
 | ||||||
|  |     sentfromcurrentuser: boolean; // Was the last message sent from the current user?.
 | ||||||
|  |     lastmessage: string; // The user's last message.
 | ||||||
|  |     lastmessagedate: number; // Timestamp for last message.
 | ||||||
|  |     messageid: number; // The unique search message id.
 | ||||||
|  |     showonlinestatus: boolean; // Show the user's online status?.
 | ||||||
|  |     isonline: boolean; // The user's online status.
 | ||||||
|  |     isread: boolean; // If the user has read the message.
 | ||||||
|  |     isblocked: boolean; // If the user has been blocked.
 | ||||||
|  |     unreadcount: number; // The number of unread messages in this conversation.
 | ||||||
|  |     conversationid: number; // The id of the conversation.
 | ||||||
|  | } & AddonMessagesMessageAreaContactCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_blocked_users. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetBlockedUsersResult = { | ||||||
|  |     users: AddonMessagesBlockedUser[]; // List of blocked users.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * User data returned by core_message_get_blocked_users. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesBlockedUser = { | ||||||
|  |     id: number; // User ID.
 | ||||||
|  |     fullname: string; // User full name.
 | ||||||
|  |     profileimageurl?: string; // User picture URL.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_contacts. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetContactsResult = { | ||||||
|  |     online: AddonMessagesGetContactsContact[]; // List of online contacts.
 | ||||||
|  |     offline: AddonMessagesGetContactsContact[]; // List of offline contacts.
 | ||||||
|  |     strangers: AddonMessagesGetContactsContact[]; // List of users that are not in the user's contact list but have sent a message.
 | ||||||
|  | } & AddonMessagesGetContactsCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * User data returned by core_message_get_contacts. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetContactsContact = { | ||||||
|  |     id: number; // User ID.
 | ||||||
|  |     fullname: string; // User full name.
 | ||||||
|  |     profileimageurl?: string; // User picture URL.
 | ||||||
|  |     profileimageurlsmall?: string; // Small user picture URL.
 | ||||||
|  |     unread: number; // Unread message count.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * User data returned by core_message_search_contacts. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesSearchContactsContact = { | ||||||
|  |     id: number; // User ID.
 | ||||||
|  |     fullname: string; // User full name.
 | ||||||
|  |     profileimageurl?: string; // User picture URL.
 | ||||||
|  |     profileimageurlsmall?: string; // Small user picture URL.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_conversation_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetConversationMessagesResult = { | ||||||
|  |     id: number; // The conversation id.
 | ||||||
|  |     members: AddonMessagesConversationMember[]; | ||||||
|  |     messages: AddonMessagesConversationMessage[]; | ||||||
|  | } & AddonMessagesGetConversationMessagesCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_conversations. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetConversationsResult = { | ||||||
|  |     conversations: AddonMessagesConversation[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_conversation_counts. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetConversationCountsResult = { | ||||||
|  |     favourites: number; // Total number of favourite conversations.
 | ||||||
|  |     types: { | ||||||
|  |         1: number; // Total number of individual conversations.
 | ||||||
|  |         2: number; // Total number of group conversations.
 | ||||||
|  |         3: number; // Total number of self conversations.
 | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_unread_conversation_counts. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetUnreadConversationCountsResult = { | ||||||
|  |     favourites: number; // Total number of unread favourite conversations.
 | ||||||
|  |     types: { | ||||||
|  |         1: number; // Total number of unread individual conversations.
 | ||||||
|  |         2: number; // Total number of unread group conversations.
 | ||||||
|  |         3: number; // Total number of unread self conversations.
 | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_user_message_preferences. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetUserMessagePreferencesResult = { | ||||||
|  |     preferences: AddonMessagesMessagePreferences; | ||||||
|  |     blocknoncontacts: number; // Privacy messaging setting to define who can message you.
 | ||||||
|  |     entertosend: boolean; // User preference for using enter to send messages.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetMessagesResult = { | ||||||
|  |     messages: AddonMessagesGetMessagesMessage[]; | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Message data returned by core_message_get_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetMessagesMessage = { | ||||||
|  |     id: number; // Message id.
 | ||||||
|  |     useridfrom: number; // User from id.
 | ||||||
|  |     useridto: number; // User to id.
 | ||||||
|  |     subject: string; // The message subject.
 | ||||||
|  |     text: string; // The message text formated.
 | ||||||
|  |     fullmessage: string; // The message.
 | ||||||
|  |     fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     fullmessagehtml: string; // The message in html.
 | ||||||
|  |     smallmessage: string; // The shorten message.
 | ||||||
|  |     notification: number; // Is a notification?.
 | ||||||
|  |     contexturl: string; // Context URL.
 | ||||||
|  |     contexturlname: string; // Context URL link name.
 | ||||||
|  |     timecreated: number; // Time created.
 | ||||||
|  |     timeread: number; // Time read.
 | ||||||
|  |     usertofullname: string; // User to full name.
 | ||||||
|  |     userfromfullname: string; // User from full name.
 | ||||||
|  |     component?: string; // The component that generated the notification.
 | ||||||
|  |     eventtype?: string; // The type of notification.
 | ||||||
|  |     customdata?: string; // Custom data to be passed to the message processor.
 | ||||||
|  | } & AddonMessagesGetMessagesMessageCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_data_for_messagearea_search_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesDataForMessageAreaSearchMessagesResult = { | ||||||
|  |     contacts: AddonMessagesMessageAreaContact[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_message_search_users. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesSearchUsersResult = { | ||||||
|  |     contacts: AddonMessagesConversationMember[]; | ||||||
|  |     noncontacts: AddonMessagesConversationMember[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_mark_message_read. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesMarkMessageReadResult = { | ||||||
|  |     messageid: number; // The id of the message in the messages table.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_send_instant_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesSendInstantMessagesMessage = { | ||||||
|  |     msgid: number; // Test this to know if it succeeds:  id of the created message if it succeeded, -1 when failed.
 | ||||||
|  |     clientmsgid?: string; // Your own id for the message.
 | ||||||
|  |     errormessage?: string; // Error message - if it failed.
 | ||||||
|  |     text?: string; // The text of the message.
 | ||||||
|  |     timecreated?: number; // The timecreated timestamp for the message.
 | ||||||
|  |     conversationid?: number; // The conversation id for this message.
 | ||||||
|  |     useridfrom?: number; // The user id who sent the message.
 | ||||||
|  |     candeletemessagesforallusers: boolean; // If the user can delete messages in the conversation for all users.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_send_messages_to_conversation. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesSendMessagesToConversationMessage = { | ||||||
|  |     id: number; // The id of the message.
 | ||||||
|  |     useridfrom: number; // The id of the user who sent the message.
 | ||||||
|  |     text: string; // The text of the message.
 | ||||||
|  |     timecreated: number; // The timecreated timestamp for the message.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Calculated data for core_message_get_contacts. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetContactsCalculatedData = { | ||||||
|  |     blocked?: AddonMessagesBlockedUser[]; // Calculated in the app. List of blocked users.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Calculated data for core_message_get_conversation_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetConversationMessagesCalculatedData = { | ||||||
|  |     canLoadMore?: boolean; // Calculated in the app. Whether more messages can be loaded.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Calculated data for message preferences. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesMessagePreferencesCalculatedData = { | ||||||
|  |     blocknoncontacts?: number; // Calculated in the app. Based on the result of core_message_get_user_message_preferences.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Calculated data for messages returned by core_message_get_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesGetMessagesMessageCalculatedData = { | ||||||
|  |     pending?: boolean; // Calculated in the app. Whether the message is pending to be sent.
 | ||||||
|  |     read?: number; // Calculated in the app. Whether the message has been read.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Calculated data for contact for message area. | ||||||
|  |  */ | ||||||
|  | export type AddonMessagesMessageAreaContactCalculatedData = { | ||||||
|  |     id?: number; // Calculated in the app. User ID.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; | |||||||
| import { CoreSyncBaseProvider } from '@classes/base-sync'; | import { CoreSyncBaseProvider } from '@classes/base-sync'; | ||||||
| import { CoreAppProvider } from '@providers/app'; | import { CoreAppProvider } from '@providers/app'; | ||||||
| import { AddonMessagesOfflineProvider } from './messages-offline'; | import { AddonMessagesOfflineProvider } from './messages-offline'; | ||||||
| import { AddonMessagesProvider } from './messages'; | import { AddonMessagesProvider, AddonMessagesConversationFormatted } from './messages'; | ||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { CoreEventsProvider } from '@providers/events'; | import { CoreEventsProvider } from '@providers/events'; | ||||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||||
| @ -258,7 +258,7 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { | |||||||
|                 // Get conversation name and add errors to warnings array.
 |                 // Get conversation name and add errors to warnings array.
 | ||||||
|                 return this.messagesProvider.getConversation(conversationId, false, false).catch(() => { |                 return this.messagesProvider.getConversation(conversationId, false, false).catch(() => { | ||||||
|                     // Ignore errors.
 |                     // Ignore errors.
 | ||||||
|                     return {}; |                     return <AddonMessagesConversationFormatted> {}; | ||||||
|                 }).then((conversation) => { |                 }).then((conversation) => { | ||||||
|                     errors.forEach((error) => { |                     errors.forEach((error) => { | ||||||
|                         warnings.push(this.translate.instant('addon.messages.warningconversationmessagenotsent', { |                         warnings.push(this.translate.instant('addon.messages.warningconversationmessagenotsent', { | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ import { CoreSitesProvider } from '@providers/sites'; | |||||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { coreSlideInOut } from '@classes/animations'; | import { coreSlideInOut } from '@classes/animations'; | ||||||
| import { AddonNotesProvider } from '../../providers/notes'; | import { AddonNotesProvider, AddonNotesNoteFormatted } from '../../providers/notes'; | ||||||
| import { AddonNotesOfflineProvider } from '../../providers/notes-offline'; | import { AddonNotesOfflineProvider } from '../../providers/notes-offline'; | ||||||
| import { AddonNotesSyncProvider } from '../../providers/notes-sync'; | import { AddonNotesSyncProvider } from '../../providers/notes-sync'; | ||||||
| 
 | 
 | ||||||
| @ -44,7 +44,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | |||||||
|     type = 'course'; |     type = 'course'; | ||||||
|     refreshIcon = 'spinner'; |     refreshIcon = 'spinner'; | ||||||
|     syncIcon = 'spinner'; |     syncIcon = 'spinner'; | ||||||
|     notes: any[]; |     notes: AddonNotesNoteFormatted[]; | ||||||
|     hasOffline = false; |     hasOffline = false; | ||||||
|     notesLoaded = false; |     notesLoaded = false; | ||||||
|     user: any; |     user: any; | ||||||
| @ -101,21 +101,21 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | |||||||
|             // Ignore errors.
 |             // Ignore errors.
 | ||||||
|         }).then(() => { |         }).then(() => { | ||||||
|             return this.notesProvider.getNotes(this.courseId, this.userId).then((notes) => { |             return this.notesProvider.getNotes(this.courseId, this.userId).then((notes) => { | ||||||
|                 notes = notes[this.type + 'notes'] || []; |                 const notesList: AddonNotesNoteFormatted[] = notes[this.type + 'notes'] || []; | ||||||
| 
 | 
 | ||||||
|                 return this.notesProvider.setOfflineDeletedNotes(notes, this.courseId).then((notes) => { |                 return this.notesProvider.setOfflineDeletedNotes(notesList, this.courseId).then((notesList) => { | ||||||
| 
 | 
 | ||||||
|                     this.hasOffline = notes.some((note) => note.offline || note.deleted); |                     this.hasOffline = notesList.some((note) => note.offline || note.deleted); | ||||||
| 
 | 
 | ||||||
|                     if (this.userId) { |                     if (this.userId) { | ||||||
|                         this.notes = notes; |                         this.notes = notesList; | ||||||
| 
 | 
 | ||||||
|                         // Get the user profile to retrieve the user image.
 |                         // Get the user profile to retrieve the user image.
 | ||||||
|                         return this.userProvider.getProfile(this.userId, this.courseId, true).then((user) => { |                         return this.userProvider.getProfile(this.userId, this.courseId, true).then((user) => { | ||||||
|                             this.user = user; |                             this.user = user; | ||||||
|                         }); |                         }); | ||||||
|                     } else { |                     } else { | ||||||
|                         return this.notesProvider.getNotesUserData(notes, this.courseId).then((notes) => { |                         return this.notesProvider.getNotesUserData(notesList, this.courseId).then((notes) => { | ||||||
|                             this.notes = notes; |                             this.notes = notes; | ||||||
|                         }); |                         }); | ||||||
|                     } |                     } | ||||||
| @ -126,7 +126,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | |||||||
|         }).finally(() => { |         }).finally(() => { | ||||||
|             let canDelete = this.notes && this.notes.length > 0; |             let canDelete = this.notes && this.notes.length > 0; | ||||||
|             if (canDelete && this.type == 'personal') { |             if (canDelete && this.type == 'personal') { | ||||||
|                 canDelete = this.notes.find((note) =>  { |                 canDelete = !!this.notes.find((note) =>  { | ||||||
|                     return note.usermodified == this.currentUserId; |                     return note.usermodified == this.currentUserId; | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
| @ -178,6 +178,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | |||||||
|     addNote(e: Event): void { |     addNote(e: Event): void { | ||||||
|         e.preventDefault(); |         e.preventDefault(); | ||||||
|         e.stopPropagation(); |         e.stopPropagation(); | ||||||
|  | 
 | ||||||
|         const modal = this.modalCtrl.create('AddonNotesAddPage', { userId: this.userId, courseId: this.courseId, type: this.type }); |         const modal = this.modalCtrl.create('AddonNotesAddPage', { userId: this.userId, courseId: this.courseId, type: this.type }); | ||||||
|         modal.onDidDismiss((data) => { |         modal.onDidDismiss((data) => { | ||||||
|             if (data && data.sent && data.type) { |             if (data && data.sent && data.type) { | ||||||
| @ -192,6 +193,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | |||||||
|                 this.typeChanged(); |                 this.typeChanged(); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  | 
 | ||||||
|         modal.present(); |         modal.present(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -201,7 +203,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | |||||||
|      * @param e Click event. |      * @param e Click event. | ||||||
|      * @param note Note to delete. |      * @param note Note to delete. | ||||||
|      */ |      */ | ||||||
|     deleteNote(e: Event, note: any): void { |     deleteNote(e: Event, note: AddonNotesNoteFormatted): void { | ||||||
|         e.preventDefault(); |         e.preventDefault(); | ||||||
|         e.stopPropagation(); |         e.stopPropagation(); | ||||||
| 
 | 
 | ||||||
| @ -226,7 +228,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | |||||||
|      * @param e Click event. |      * @param e Click event. | ||||||
|      * @param note Note to delete. |      * @param note Note to delete. | ||||||
|      */ |      */ | ||||||
|     undoDeleteNote(e: Event, note: any): void { |     undoDeleteNote(e: Event, note: AddonNotesNoteFormatted): void { | ||||||
|         e.preventDefault(); |         e.preventDefault(); | ||||||
|         e.stopPropagation(); |         e.stopPropagation(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ import { TranslateService } from '@ngx-translate/core'; | |||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { AddonNotesOfflineProvider } from './notes-offline'; | import { AddonNotesOfflineProvider } from './notes-offline'; | ||||||
| import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | ||||||
|  | import { CoreWSExternalWarning } from '@providers/ws'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to handle notes. |  * Service to handle notes. | ||||||
| @ -119,9 +120,9 @@ export class AddonNotesProvider { | |||||||
|      * @return Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that notes |      * @return Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that notes | ||||||
|      *         have been added, the resolve param can contain errors for notes not sent. |      *         have been added, the resolve param can contain errors for notes not sent. | ||||||
|      */ |      */ | ||||||
|     addNotesOnline(notes: any[], siteId?: string): Promise<any> { |     addNotesOnline(notes: any[], siteId?: string): Promise<AddonNotesCreateNotesNote[]> { | ||||||
|         if (!notes || !notes.length) { |         if (!notes || !notes.length) { | ||||||
|             return Promise.resolve(); |             return Promise.resolve([]); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| @ -142,7 +143,7 @@ export class AddonNotesProvider { | |||||||
|      * @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes |      * @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes | ||||||
|      *         have been deleted, the resolve param can contain errors for notes not deleted. |      *         have been deleted, the resolve param can contain errors for notes not deleted. | ||||||
|      */ |      */ | ||||||
|     deleteNote(note: any, courseId: number, siteId?: string): Promise<void> { |     deleteNote(note: AddonNotesNoteFormatted, courseId: number, siteId?: string): Promise<void> { | ||||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); |         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
| 
 | 
 | ||||||
|         if (note.offline) { |         if (note.offline) { | ||||||
| @ -190,7 +191,7 @@ export class AddonNotesProvider { | |||||||
|                 notes: noteIds |                 notes: noteIds | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.write('core_notes_delete_notes', data).then((response) => { |             return site.write('core_notes_delete_notes', data).then((response: CoreWSExternalWarning[]) => { | ||||||
|                 // A note was deleted, invalidate the course notes.
 |                 // A note was deleted, invalidate the course notes.
 | ||||||
|                 return this.invalidateNotes(courseId, undefined, siteId).catch(() => { |                 return this.invalidateNotes(courseId, undefined, siteId).catch(() => { | ||||||
|                     // Ignore errors.
 |                     // Ignore errors.
 | ||||||
| @ -288,7 +289,9 @@ export class AddonNotesProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise to be resolved when the notes are retrieved. |      * @return Promise to be resolved when the notes are retrieved. | ||||||
|      */ |      */ | ||||||
|     getNotes(courseId: number, userId?: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string): Promise<any> { |     getNotes(courseId: number, userId?: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string) | ||||||
|  |             : Promise<AddonNotesGetCourseNotesResult> { | ||||||
|  | 
 | ||||||
|         this.logger.debug('Get notes for course ' + courseId); |         this.logger.debug('Get notes for course ' + courseId); | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| @ -310,7 +313,7 @@ export class AddonNotesProvider { | |||||||
|                 preSets.emergencyCache = false; |                 preSets.emergencyCache = false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('core_notes_get_course_notes', data, preSets).then((notes) => { |             return site.read('core_notes_get_course_notes', data, preSets).then((notes: AddonNotesGetCourseNotesResult) => { | ||||||
|                 if (onlyOnline) { |                 if (onlyOnline) { | ||||||
|                     return notes; |                     return notes; | ||||||
|                 } |                 } | ||||||
| @ -339,9 +342,11 @@ export class AddonNotesProvider { | |||||||
|      * @param notes Array of notes. |      * @param notes Array of notes. | ||||||
|      * @param courseId ID of the course the notes belong to. |      * @param courseId ID of the course the notes belong to. | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return [description] |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     setOfflineDeletedNotes(notes: any[], courseId: number, siteId?: string): Promise<any> { |     setOfflineDeletedNotes(notes: AddonNotesNoteFormatted[], courseId: number, siteId?: string) | ||||||
|  |             : Promise<AddonNotesNoteFormatted[]> { | ||||||
|  | 
 | ||||||
|         return this.notesOffline.getCourseDeletedNotes(courseId, siteId).then((deletedNotes) => { |         return this.notesOffline.getCourseDeletedNotes(courseId, siteId).then((deletedNotes) => { | ||||||
|             notes.forEach((note) => { |             notes.forEach((note) => { | ||||||
|                 note.deleted = deletedNotes.some((n) => n.noteid == note.id); |                 note.deleted = deletedNotes.some((n) => n.noteid == note.id); | ||||||
| @ -358,7 +363,7 @@ export class AddonNotesProvider { | |||||||
|      * @param courseId ID of the course the notes belong to. |      * @param courseId ID of the course the notes belong to. | ||||||
|      * @return Promise always resolved. Resolve param is the formatted notes. |      * @return Promise always resolved. Resolve param is the formatted notes. | ||||||
|      */ |      */ | ||||||
|     getNotesUserData(notes: any[], courseId: number): Promise<any> { |     getNotesUserData(notes: AddonNotesNoteFormatted[], courseId: number): Promise<AddonNotesNoteFormatted[]> { | ||||||
|         const promises = notes.map((note) => { |         const promises = notes.map((note) => { | ||||||
|             // Get the user profile to retrieve the user image.
 |             // Get the user profile to retrieve the user image.
 | ||||||
|             return this.userProvider.getProfile(note.userid, note.courseid, true).then((user) => { |             return this.userProvider.getProfile(note.userid, note.courseid, true).then((user) => { | ||||||
| @ -400,7 +405,7 @@ export class AddonNotesProvider { | |||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved when the WS call is successful. |      * @return Promise resolved when the WS call is successful. | ||||||
|      */ |      */ | ||||||
|     logView(courseId: number, userId?: number, siteId?: string): Promise<any> { |     logView(courseId: number, userId?: number, siteId?: string): Promise<AddonNotesViewNotesResult> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const params = { |             const params = { | ||||||
|                 courseid: courseId, |                 courseid: courseId, | ||||||
| @ -413,3 +418,57 @@ export class AddonNotesProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Note data returned by core_notes_get_course_notes. | ||||||
|  |  */ | ||||||
|  | export type AddonNotesNote = { | ||||||
|  |     id: number; // Id of this note.
 | ||||||
|  |     courseid: number; // Id of the course.
 | ||||||
|  |     userid: number; // User id.
 | ||||||
|  |     content: string; // The content text formated.
 | ||||||
|  |     format: number; // Content format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     created: number; // Time created (timestamp).
 | ||||||
|  |     lastmodified: number; // Time of last modification (timestamp).
 | ||||||
|  |     usermodified: number; // User id of the creator of this note.
 | ||||||
|  |     publishstate: string; // State of the note (i.e. draft, public, site).
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_notes_get_course_notes. | ||||||
|  |  */ | ||||||
|  | export type AddonNotesGetCourseNotesResult = { | ||||||
|  |     sitenotes?: AddonNotesNote[]; // Site notes.
 | ||||||
|  |     coursenotes?: AddonNotesNote[]; // Couse notes.
 | ||||||
|  |     personalnotes?: AddonNotesNote[]; // Personal notes.
 | ||||||
|  |     canmanagesystemnotes?: boolean; // Whether the user can manage notes at system level.
 | ||||||
|  |     canmanagecoursenotes?: boolean; // Whether the user can manage notes at the given course.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Note returned by WS core_notes_create_notes. | ||||||
|  |  */ | ||||||
|  | export type AddonNotesCreateNotesNote = { | ||||||
|  |     clientnoteid?: string; // Your own id for the note.
 | ||||||
|  |     noteid: number; // ID of the created note when successful, -1 when failed.
 | ||||||
|  |     errormessage?: string; // Error message - if failed.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_notes_view_notes. | ||||||
|  |  */ | ||||||
|  | export type AddonNotesViewNotesResult = { | ||||||
|  |     status: boolean; // Status: true if success.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Notes with some calculated data. | ||||||
|  |  */ | ||||||
|  | export type AddonNotesNoteFormatted = AddonNotesNote & { | ||||||
|  |     offline?: boolean; // Calculated in the app. Whether it's an offline note.
 | ||||||
|  |     deleted?: boolean; // Calculated in the app. Whether the note was deleted in offline.
 | ||||||
|  |     userfullname?: string; // Calculated in the app. Full name of the user the note refers to.
 | ||||||
|  |     userprofileimageurl?: string; // Calculated in the app. Avatar url of the user the note refers to.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; | |||||||
| import { CoreEventsProvider, CoreEventObserver } from '@providers/events'; | import { CoreEventsProvider, CoreEventObserver } from '@providers/events'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| import { AddonNotificationsProvider } from '../../providers/notifications'; | import { AddonNotificationsProvider, AddonNotificationsAnyNotification } from '../../providers/notifications'; | ||||||
| import { AddonNotificationsHelperProvider } from '../../providers/helper'; | import { AddonNotificationsHelperProvider } from '../../providers/helper'; | ||||||
| import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; | import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; | ||||||
| 
 | 
 | ||||||
| @ -34,7 +34,7 @@ import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers | |||||||
| }) | }) | ||||||
| export class AddonNotificationsListPage { | export class AddonNotificationsListPage { | ||||||
| 
 | 
 | ||||||
|     notifications = []; |     notifications: AddonNotificationsAnyNotification[] = []; | ||||||
|     notificationsLoaded = false; |     notificationsLoaded = false; | ||||||
|     canLoadMore = false; |     canLoadMore = false; | ||||||
|     loadMoreError = false; |     loadMoreError = false; | ||||||
| @ -130,11 +130,12 @@ export class AddonNotificationsListPage { | |||||||
|      * |      * | ||||||
|      * @param notifications Array of notification objects. |      * @param notifications Array of notification objects. | ||||||
|      */ |      */ | ||||||
|     protected markNotificationsAsRead(notifications: any[]): void { |     protected markNotificationsAsRead(notifications: AddonNotificationsAnyNotification[]): void { | ||||||
|  | 
 | ||||||
|         let promise; |         let promise; | ||||||
| 
 | 
 | ||||||
|         if (notifications.length > 0) { |         if (notifications.length > 0) { | ||||||
|             const promises = notifications.map((notification) => { |             const promises: Promise<any>[] = notifications.map((notification) => { | ||||||
|                 if (notification.read) { |                 if (notification.read) { | ||||||
|                     // Already read, don't mark it.
 |                     // Already read, don't mark it.
 | ||||||
|                     return Promise.resolve(); |                     return Promise.resolve(); | ||||||
| @ -202,7 +203,7 @@ export class AddonNotificationsListPage { | |||||||
|      * |      * | ||||||
|      * @param notification The notification object. |      * @param notification The notification object. | ||||||
|      */ |      */ | ||||||
|     protected formatText(notification: any): void { |     protected formatText(notification: AddonNotificationsAnyNotification): void { | ||||||
|         const text = notification.mobiletext.replace(/-{4,}/ig, ''); |         const text = notification.mobiletext.replace(/-{4,}/ig, ''); | ||||||
|         notification.mobiletext = this.textUtils.replaceNewLines(text, '<br>'); |         notification.mobiletext = this.textUtils.replaceNewLines(text, '<br>'); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -14,7 +14,11 @@ | |||||||
| 
 | 
 | ||||||
| import { Component, OnDestroy, Optional } from '@angular/core'; | import { Component, OnDestroy, Optional } from '@angular/core'; | ||||||
| import { IonicPage, NavController } from 'ionic-angular'; | import { IonicPage, NavController } from 'ionic-angular'; | ||||||
| import { AddonNotificationsProvider } from '../../providers/notifications'; | import { | ||||||
|  |     AddonNotificationsProvider, AddonNotificationsNotificationPreferences, AddonNotificationsNotificationPreferencesProcessor, | ||||||
|  |     AddonNotificationsNotificationPreferencesComponent, AddonNotificationsNotificationPreferencesNotification, | ||||||
|  |     AddonNotificationsNotificationPreferencesNotificationProcessorState | ||||||
|  | } from '../../providers/notifications'; | ||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreSettingsHelper } from '@core/settings/providers/helper'; | import { CoreSettingsHelper } from '@core/settings/providers/helper'; | ||||||
| @ -38,10 +42,10 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view'; | |||||||
| export class AddonNotificationsSettingsPage implements OnDestroy { | export class AddonNotificationsSettingsPage implements OnDestroy { | ||||||
|     protected updateTimeout: any; |     protected updateTimeout: any; | ||||||
| 
 | 
 | ||||||
|     components: any[]; |     components: AddonNotificationsNotificationPreferencesComponent[]; | ||||||
|     preferences: any; |     preferences: AddonNotificationsNotificationPreferences; | ||||||
|     preferencesLoaded: boolean; |     preferencesLoaded: boolean; | ||||||
|     currentProcessor: any; |     currentProcessor: AddonNotificationsNotificationPreferencesProcessorFormatted; | ||||||
|     notifPrefsEnabled: boolean; |     notifPrefsEnabled: boolean; | ||||||
|     canChangeSound: boolean; |     canChangeSound: boolean; | ||||||
|     notificationSound: boolean; |     notificationSound: boolean; | ||||||
| @ -99,7 +103,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | |||||||
|             // Get display data of message output handlers (thery are displayed in the context menu),
 |             // Get display data of message output handlers (thery are displayed in the context menu),
 | ||||||
|             this.processorHandlers = []; |             this.processorHandlers = []; | ||||||
|             if (preferences.processors) { |             if (preferences.processors) { | ||||||
|                 preferences.processors.forEach((processor) => { |                 preferences.processors.forEach((processor: AddonNotificationsNotificationPreferencesProcessorFormatted) => { | ||||||
|                     processor.supported = this.messageOutputDelegate.hasHandler(processor.name, true); |                     processor.supported = this.messageOutputDelegate.hasHandler(processor.name, true); | ||||||
|                     if (processor.hassettings && processor.supported) { |                     if (processor.hassettings && processor.supported) { | ||||||
|                         this.processorHandlers.push(this.messageOutputDelegate.getDisplayData(processor)); |                         this.processorHandlers.push(this.messageOutputDelegate.getDisplayData(processor)); | ||||||
| @ -118,7 +122,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | |||||||
|      * |      * | ||||||
|      * @param processor Processor object. |      * @param processor Processor object. | ||||||
|      */ |      */ | ||||||
|     protected loadProcessor(processor: any): void { |     protected loadProcessor(processor: AddonNotificationsNotificationPreferencesProcessorFormatted): void { | ||||||
|         if (!processor) { |         if (!processor) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -191,8 +195,9 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | |||||||
|      * @param notification Notification object. |      * @param notification Notification object. | ||||||
|      * @param state State name, ['loggedin', 'loggedoff']. |      * @param state State name, ['loggedin', 'loggedoff']. | ||||||
|      */ |      */ | ||||||
|     changePreference(notification: any, state: string): void { |     changePreference(notification: AddonNotificationsNotificationPreferencesNotificationFormatted, state: string): void { | ||||||
|         const processorState = notification.currentProcessor[state]; |         const processorState: AddonNotificationsNotificationPreferencesNotificationProcessorStateFormatted = | ||||||
|  |                 notification.currentProcessor[state]; | ||||||
|         const preferenceName = notification.preferencekey + '_' + processorState.name; |         const preferenceName = notification.preferencekey + '_' + processorState.name; | ||||||
|         let value; |         let value; | ||||||
| 
 | 
 | ||||||
| @ -211,6 +216,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         processorState.updating = true; |         processorState.updating = true; | ||||||
|  | 
 | ||||||
|         this.userProvider.updateUserPreference(preferenceName, value).then(() => { |         this.userProvider.updateUserPreference(preferenceName, value).then(() => { | ||||||
|             // Update the preferences since they were modified.
 |             // Update the preferences since they were modified.
 | ||||||
|             this.updatePreferencesAfterDelay(); |             this.updatePreferencesAfterDelay(); | ||||||
| @ -264,3 +270,25 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Notification preferences notification with some calculated data. | ||||||
|  |  */ | ||||||
|  | type AddonNotificationsNotificationPreferencesNotificationFormatted = AddonNotificationsNotificationPreferencesNotification & { | ||||||
|  |     currentProcessor?: AddonNotificationsNotificationPreferencesProcessorFormatted; // Calculated in the app. Current processor.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Notification preferences processor with some calculated data. | ||||||
|  |  */ | ||||||
|  | type AddonNotificationsNotificationPreferencesProcessorFormatted = AddonNotificationsNotificationPreferencesProcessor & { | ||||||
|  |     supported?: boolean; // Calculated in the app. Whether the processor is supported in the app.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * State in notification processor in notification preferences component with some calculated data. | ||||||
|  |  */ | ||||||
|  | type AddonNotificationsNotificationPreferencesNotificationProcessorStateFormatted = | ||||||
|  |         AddonNotificationsNotificationPreferencesNotificationProcessorState & { | ||||||
|  |     updating?: boolean; // Calculated in the app. Whether the state is being updated.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -14,7 +14,9 @@ | |||||||
| 
 | 
 | ||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { AddonNotificationsProvider } from './notifications'; | import { | ||||||
|  |     AddonNotificationsProvider, AddonNotificationsAnyNotification, AddonNotificationsGetMessagesMessage | ||||||
|  | } from './notifications'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service that provides some helper functions for notifications. |  * Service that provides some helper functions for notifications. | ||||||
| @ -37,7 +39,7 @@ export class AddonNotificationsHelperProvider { | |||||||
|      * @return Promise resolved with notifications and if can load more. |      * @return Promise resolved with notifications and if can load more. | ||||||
|      */ |      */ | ||||||
|     getNotifications(notifications: any[], limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, |     getNotifications(notifications: any[], limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, | ||||||
|             siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> { |             siteId?: string): Promise<{notifications: AddonNotificationsAnyNotification[], canLoadMore: boolean}> { | ||||||
| 
 | 
 | ||||||
|         notifications = notifications || []; |         notifications = notifications || []; | ||||||
|         limit = limit || AddonNotificationsProvider.LIST_LIMIT; |         limit = limit || AddonNotificationsProvider.LIST_LIMIT; | ||||||
| @ -80,7 +82,7 @@ export class AddonNotificationsHelperProvider { | |||||||
|                         promise = Promise.resolve(unread); |                         promise = Promise.resolve(unread); | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     return promise.then((notifications) => { |                     return promise.then((notifications: AddonNotificationsGetMessagesMessage[]) => { | ||||||
|                         return { |                         return { | ||||||
|                             notifications: notifications, |                             notifications: notifications, | ||||||
|                             canLoadMore: notifications.length >= limit |                             canLoadMore: notifications.length >= limit | ||||||
|  | |||||||
| @ -20,8 +20,9 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; | |||||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; | import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; | ||||||
| import { AddonMessagesProvider } from '@addon/messages/providers/messages'; | import { AddonMessagesProvider, AddonMessagesMarkMessageReadResult } from '@addon/messages/providers/messages'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
|  | import { CoreWSExternalWarning } from '@providers/ws'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to handle notifications. |  * Service to handle notifications. | ||||||
| @ -51,14 +52,13 @@ export class AddonNotificationsProvider { | |||||||
|      * @param read Whether the notifications are read or unread. |      * @param read Whether the notifications are read or unread. | ||||||
|      * @return Promise resolved with notifications. |      * @return Promise resolved with notifications. | ||||||
|      */ |      */ | ||||||
|     protected formatNotificationsData(notifications: any[], read?: boolean): Promise<any> { |     protected formatNotificationsData(notifications: AddonNotificationsAnyNotification[], read?: boolean): Promise<any> { | ||||||
|  | 
 | ||||||
|         const promises = notifications.map((notification) => { |         const promises = notifications.map((notification) => { | ||||||
| 
 | 
 | ||||||
|             // Set message to show.
 |             // Set message to show.
 | ||||||
|             if (notification.component && notification.component == 'mod_forum') { |             if (notification.component && notification.component == 'mod_forum') { | ||||||
|                 notification.mobiletext = notification.smallmessage; |                 notification.mobiletext = notification.smallmessage; | ||||||
|             } else if (notification.component && notification.component == 'moodle' && notification.name == 'insights') { |  | ||||||
|                 notification.mobiletext = notification.fullmessagehtml; |  | ||||||
|             } else { |             } else { | ||||||
|                 notification.mobiletext = notification.fullmessage; |                 notification.mobiletext = notification.fullmessage; | ||||||
|             } |             } | ||||||
| @ -117,7 +117,7 @@ export class AddonNotificationsProvider { | |||||||
|      * @param siteId Site ID. If not defined, use current site. |      * @param siteId Site ID. If not defined, use current site. | ||||||
|      * @return Promise resolved with the notification preferences. |      * @return Promise resolved with the notification preferences. | ||||||
|      */ |      */ | ||||||
|     getNotificationPreferences(siteId?: string): Promise<any> { |     getNotificationPreferences(siteId?: string): Promise<AddonNotificationsNotificationPreferences> { | ||||||
|         this.logger.debug('Get notification preferences'); |         this.logger.debug('Get notification preferences'); | ||||||
| 
 | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| @ -126,7 +126,9 @@ export class AddonNotificationsProvider { | |||||||
|                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES |                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return site.read('core_message_get_user_notification_preferences', {}, preSets).then((data) => { |             return site.read('core_message_get_user_notification_preferences', {}, preSets) | ||||||
|  |                     .then((data: AddonNotificationsGetUserNotificationPreferencesResult) => { | ||||||
|  | 
 | ||||||
|                 return data.preferences; |                 return data.preferences; | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
| @ -154,7 +156,7 @@ export class AddonNotificationsProvider { | |||||||
|      * @return Promise resolved with notifications. |      * @return Promise resolved with notifications. | ||||||
|      */ |      */ | ||||||
|     getNotifications(read: boolean, limitFrom: number, limitNumber: number = 0, toDisplay: boolean = true, |     getNotifications(read: boolean, limitFrom: number, limitNumber: number = 0, toDisplay: boolean = true, | ||||||
|             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> { |             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<AddonNotificationsGetMessagesMessage[]> { | ||||||
|         limitNumber = limitNumber || AddonNotificationsProvider.LIST_LIMIT; |         limitNumber = limitNumber || AddonNotificationsProvider.LIST_LIMIT; | ||||||
|         this.logger.debug('Get ' + (read ? 'read' : 'unread') + ' notifications from ' + limitFrom + '. Limit: ' + limitNumber); |         this.logger.debug('Get ' + (read ? 'read' : 'unread') + ' notifications from ' + limitFrom + '. Limit: ' + limitNumber); | ||||||
| 
 | 
 | ||||||
| @ -176,7 +178,7 @@ export class AddonNotificationsProvider { | |||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             // Get unread notifications.
 |             // Get unread notifications.
 | ||||||
|             return site.read('core_message_get_messages', data, preSets).then((response) => { |             return site.read('core_message_get_messages', data, preSets).then((response: AddonNotificationsGetMessagesResult) => { | ||||||
|                 if (response.messages) { |                 if (response.messages) { | ||||||
|                     const notifications = response.messages; |                     const notifications = response.messages; | ||||||
| 
 | 
 | ||||||
| @ -209,7 +211,7 @@ export class AddonNotificationsProvider { | |||||||
|      * @since 3.2 |      * @since 3.2 | ||||||
|      */ |      */ | ||||||
|     getPopupNotifications(offset: number, limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, |     getPopupNotifications(offset: number, limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, | ||||||
|             siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> { |             siteId?: string): Promise<{notifications: AddonNotificationsPopupNotificationFormatted[], canLoadMore: boolean}> { | ||||||
| 
 | 
 | ||||||
|         limit = limit || AddonNotificationsProvider.LIST_LIMIT; |         limit = limit || AddonNotificationsProvider.LIST_LIMIT; | ||||||
| 
 | 
 | ||||||
| @ -230,17 +232,17 @@ export class AddonNotificationsProvider { | |||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|             // Get notifications.
 |             // Get notifications.
 | ||||||
|             return site.read('message_popup_get_popup_notifications', data, preSets).then((response) => { |             return site.read('message_popup_get_popup_notifications', data, preSets) | ||||||
|  |                     .then((response: AddonNotificationsGetPopupNotificationsResult) => { | ||||||
|  | 
 | ||||||
|                 if (response.notifications) { |                 if (response.notifications) { | ||||||
|                     const result: any = { |                     const result = { | ||||||
|                             canLoadMore: response.notifications.length > limit |                             canLoadMore: response.notifications.length > limit, | ||||||
|                         }, |                             notifications: response.notifications.slice(0, limit) | ||||||
|                         notifications = response.notifications.slice(0, limit); |                         }; | ||||||
| 
 | 
 | ||||||
|                     result.notifications = notifications; |                     return this.formatNotificationsData(result.notifications).then(() => { | ||||||
| 
 |                         const first = result.notifications[0]; | ||||||
|                     return this.formatNotificationsData(notifications).then(() => { |  | ||||||
|                         const first = notifications[0]; |  | ||||||
| 
 | 
 | ||||||
|                         if (this.appProvider.isDesktop() && toDisplay && offset === 0 && first && !first.read) { |                         if (this.appProvider.isDesktop() && toDisplay && offset === 0 && first && !first.read) { | ||||||
|                             // Store the last received notification. Don't block the user for this.
 |                             // Store the last received notification. Don't block the user for this.
 | ||||||
| @ -269,7 +271,7 @@ export class AddonNotificationsProvider { | |||||||
|      * @return Promise resolved with notifications. |      * @return Promise resolved with notifications. | ||||||
|      */ |      */ | ||||||
|     getReadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true, |     getReadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true, | ||||||
|             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> { |             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<AddonNotificationsGetMessagesMessage[]> { | ||||||
|         return this.getNotifications(true, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId); |         return this.getNotifications(true, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -285,7 +287,7 @@ export class AddonNotificationsProvider { | |||||||
|      * @return Promise resolved with notifications. |      * @return Promise resolved with notifications. | ||||||
|      */ |      */ | ||||||
|     getUnreadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true, |     getUnreadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true, | ||||||
|             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> { |             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<AddonNotificationsGetMessagesMessage[]> { | ||||||
|         return this.getNotifications(false, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId); |         return this.getNotifications(false, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -349,7 +351,7 @@ export class AddonNotificationsProvider { | |||||||
|      * @return Resolved when done. |      * @return Resolved when done. | ||||||
|      * @since 3.2 |      * @since 3.2 | ||||||
|      */ |      */ | ||||||
|     markAllNotificationsAsRead(): Promise<any> { |     markAllNotificationsAsRead(): Promise<boolean> { | ||||||
|         const params = { |         const params = { | ||||||
|             useridto: this.sitesProvider.getCurrentSiteUserId() |             useridto: this.sitesProvider.getCurrentSiteUserId() | ||||||
|         }; |         }; | ||||||
| @ -362,10 +364,12 @@ export class AddonNotificationsProvider { | |||||||
|      * |      * | ||||||
|      * @param notificationId ID of notification to mark as read |      * @param notificationId ID of notification to mark as read | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Resolved when done. |      * @return Promise resolved when done. | ||||||
|      * @since 3.5 |      * @since 3.5 | ||||||
|      */ |      */ | ||||||
|     markNotificationRead(notificationId: number, siteId?: string): Promise<any> { |     markNotificationRead(notificationId: number, siteId?: string) | ||||||
|  |             : Promise<AddonNotificationsMarkNotificationReadResult | AddonMessagesMarkMessageReadResult> { | ||||||
|  | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
| 
 | 
 | ||||||
|             if (site.wsAvailable('core_message_mark_notification_read')) { |             if (site.wsAvailable('core_message_mark_notification_read')) { | ||||||
| @ -436,3 +440,179 @@ export class AddonNotificationsProvider { | |||||||
|         return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_user_notification_preferences'); |         return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_user_notification_preferences'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Preferences returned by core_message_get_user_notification_preferences. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsNotificationPreferences = { | ||||||
|  |     userid: number; // User id.
 | ||||||
|  |     disableall: number | boolean; // Whether all the preferences are disabled.
 | ||||||
|  |     processors: AddonNotificationsNotificationPreferencesProcessor[]; // Config form values.
 | ||||||
|  |     components: AddonNotificationsNotificationPreferencesComponent[]; // Available components.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Processor in notification preferences. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsNotificationPreferencesProcessor = { | ||||||
|  |     displayname: string; // Display name.
 | ||||||
|  |     name: string; // Processor name.
 | ||||||
|  |     hassettings: boolean; // Whether has settings.
 | ||||||
|  |     contextid: number; // Context id.
 | ||||||
|  |     userconfigured: number; // Whether is configured by the user.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Component in notification preferences. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsNotificationPreferencesComponent = { | ||||||
|  |     displayname: string; // Display name.
 | ||||||
|  |     notifications: AddonNotificationsNotificationPreferencesNotification[]; // List of notificaitons for the component.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Notification processor in notification preferences component. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsNotificationPreferencesNotification = { | ||||||
|  |     displayname: string; // Display name.
 | ||||||
|  |     preferencekey: string; // Preference key.
 | ||||||
|  |     processors: AddonNotificationsNotificationPreferencesNotificationProcessor[]; // Processors values for this notification.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Notification processor in notification preferences component. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsNotificationPreferencesNotificationProcessor = { | ||||||
|  |     displayname: string; // Display name.
 | ||||||
|  |     name: string; // Processor name.
 | ||||||
|  |     locked: boolean; // Is locked by admin?.
 | ||||||
|  |     lockedmessage?: string; // Text to display if locked.
 | ||||||
|  |     userconfigured: number; // Is configured?.
 | ||||||
|  |     loggedin: AddonNotificationsNotificationPreferencesNotificationProcessorState; | ||||||
|  |     loggedoff: AddonNotificationsNotificationPreferencesNotificationProcessorState; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * State in notification processor in notification preferences component. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsNotificationPreferencesNotificationProcessorState = { | ||||||
|  |     name: string; // Name.
 | ||||||
|  |     displayname: string; // Display name.
 | ||||||
|  |     checked: boolean; // Is checked?.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsGetMessagesResult = { | ||||||
|  |     messages: AddonNotificationsGetMessagesMessage[]; | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Message data returned by core_message_get_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsGetMessagesMessage = { | ||||||
|  |     id: number; // Message id.
 | ||||||
|  |     useridfrom: number; // User from id.
 | ||||||
|  |     useridto: number; // User to id.
 | ||||||
|  |     subject: string; // The message subject.
 | ||||||
|  |     text: string; // The message text formated.
 | ||||||
|  |     fullmessage: string; // The message.
 | ||||||
|  |     fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     fullmessagehtml: string; // The message in html.
 | ||||||
|  |     smallmessage: string; // The shorten message.
 | ||||||
|  |     notification: number; // Is a notification?.
 | ||||||
|  |     contexturl: string; // Context URL.
 | ||||||
|  |     contexturlname: string; // Context URL link name.
 | ||||||
|  |     timecreated: number; // Time created.
 | ||||||
|  |     timeread: number; // Time read.
 | ||||||
|  |     usertofullname: string; // User to full name.
 | ||||||
|  |     userfromfullname: string; // User from full name.
 | ||||||
|  |     component?: string; // @since 3.7. The component that generated the notification.
 | ||||||
|  |     eventtype?: string; // @since 3.7. The type of notification.
 | ||||||
|  |     customdata?: any; // @since 3.7. Custom data to be passed to the message processor.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Message data returned by core_message_get_messages with some calculated data. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsGetMessagesMessageFormatted = | ||||||
|  |         AddonNotificationsGetMessagesMessage & AddonNotificationsNotificationCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS message_popup_get_popup_notifications. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsGetPopupNotificationsResult = { | ||||||
|  |     notifications: AddonNotificationsPopupNotification[]; | ||||||
|  |     unreadcount: number; // The number of unread message for the given user.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Notification returned by message_popup_get_popup_notifications. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsPopupNotification = { | ||||||
|  |     id: number; // Notification id (this is not guaranteed to be unique within this result set).
 | ||||||
|  |     useridfrom: number; // User from id.
 | ||||||
|  |     useridto: number; // User to id.
 | ||||||
|  |     subject: string; // The notification subject.
 | ||||||
|  |     shortenedsubject: string; // The notification subject shortened with ellipsis.
 | ||||||
|  |     text: string; // The message text formated.
 | ||||||
|  |     fullmessage: string; // The message.
 | ||||||
|  |     fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     fullmessagehtml: string; // The message in html.
 | ||||||
|  |     smallmessage: string; // The shorten message.
 | ||||||
|  |     contexturl: string; // Context URL.
 | ||||||
|  |     contexturlname: string; // Context URL link name.
 | ||||||
|  |     timecreated: number; // Time created.
 | ||||||
|  |     timecreatedpretty: string; // Time created in a pretty format.
 | ||||||
|  |     timeread: number; // Time read.
 | ||||||
|  |     read: boolean; // Notification read status.
 | ||||||
|  |     deleted: boolean; // Notification deletion status.
 | ||||||
|  |     iconurl: string; // URL for notification icon.
 | ||||||
|  |     component?: string; // The component that generated the notification.
 | ||||||
|  |     eventtype?: string; // The type of notification.
 | ||||||
|  |     customdata?: any; // @since 3.7. Custom data to be passed to the message processor.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Notification returned by message_popup_get_popup_notifications. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsPopupNotificationFormatted = | ||||||
|  |         AddonNotificationsPopupNotification & AddonNotificationsNotificationCalculatedData; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Any kind of notification that can be retrieved. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsAnyNotification = | ||||||
|  |         AddonNotificationsPopupNotificationFormatted | AddonNotificationsGetMessagesMessageFormatted; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_get_user_notification_preferences. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsGetUserNotificationPreferencesResult = { | ||||||
|  |     preferences: AddonNotificationsNotificationPreferences; | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Result of WS core_message_mark_notification_read. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsMarkNotificationReadResult = { | ||||||
|  |     notificationid: number; // Id of the notification.
 | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Calculated data for messages returned by core_message_get_messages. | ||||||
|  |  */ | ||||||
|  | export type AddonNotificationsNotificationCalculatedData = { | ||||||
|  |     mobiletext?: string; // Calculated in the app. Text to display for the notification.
 | ||||||
|  |     moodlecomponent?: string; // Calculated in the app. Moodle's component.
 | ||||||
|  |     notif?: number; // Calculated in the app. Whether it's a notification.
 | ||||||
|  |     notification?: number; // Calculated in the app in some cases. Whether it's a notification.
 | ||||||
|  |     read?: boolean; // Calculated in the app. Whether the notifications is read.
 | ||||||
|  |     courseid?: number; // Calculated in the app. Course the notification belongs to.
 | ||||||
|  |     profileimageurlfrom?: string; // Calculated in the app. Avatar of user that sent the notification.
 | ||||||
|  |     userfromfullname?: string; // Calculated in the app in some cases. User from full name.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -407,3 +407,27 @@ export class CoreCommentsProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by comment_area_exporter. | ||||||
|  |  */ | ||||||
|  | export type CoreCommentsArea = { | ||||||
|  |     component: string; // Component.
 | ||||||
|  |     commentarea: string; // Commentarea.
 | ||||||
|  |     itemid: number; // Itemid.
 | ||||||
|  |     courseid: number; // Courseid.
 | ||||||
|  |     contextid: number; // Contextid.
 | ||||||
|  |     cid: string; // Cid.
 | ||||||
|  |     autostart: boolean; // Autostart.
 | ||||||
|  |     canpost: boolean; // Canpost.
 | ||||||
|  |     canview: boolean; // Canview.
 | ||||||
|  |     count: number; // Count.
 | ||||||
|  |     collapsediconkey: string; // Collapsediconkey.
 | ||||||
|  |     displaytotalcount: boolean; // Displaytotalcount.
 | ||||||
|  |     displaycancel: boolean; // Displaycancel.
 | ||||||
|  |     fullwidth: boolean; // Fullwidth.
 | ||||||
|  |     linktext: string; // Linktext.
 | ||||||
|  |     notoggle: boolean; // Notoggle.
 | ||||||
|  |     template: string; // Template.
 | ||||||
|  |     canpostorhascomments: boolean; // Canpostorhascomments.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1135,3 +1135,38 @@ export class CoreCourseProvider { | |||||||
|         }, siteId); |         }, siteId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by course_summary_exporter. | ||||||
|  |  */ | ||||||
|  | export type CoreCourseSummary = { | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     fullname: string; // Fullname.
 | ||||||
|  |     shortname: string; // Shortname.
 | ||||||
|  |     idnumber: string; // Idnumber.
 | ||||||
|  |     summary: string; // Summary.
 | ||||||
|  |     summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     startdate: number; // Startdate.
 | ||||||
|  |     enddate: number; // Enddate.
 | ||||||
|  |     visible: boolean; // Visible.
 | ||||||
|  |     fullnamedisplay: string; // Fullnamedisplay.
 | ||||||
|  |     viewurl: string; // Viewurl.
 | ||||||
|  |     courseimage: string; // Courseimage.
 | ||||||
|  |     progress?: number; // Progress.
 | ||||||
|  |     hasprogress: boolean; // Hasprogress.
 | ||||||
|  |     isfavourite: boolean; // Isfavourite.
 | ||||||
|  |     hidden: boolean; // Hidden.
 | ||||||
|  |     timeaccess?: number; // Timeaccess.
 | ||||||
|  |     showshortname: boolean; // Showshortname.
 | ||||||
|  |     coursecategory: string; // Coursecategory.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by course_module_summary_exporter. | ||||||
|  |  */ | ||||||
|  | export type CoreCourseModuleSummary = { | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     name: string; // Name.
 | ||||||
|  |     url?: string; // Url.
 | ||||||
|  |     iconurl: string; // Iconurl.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -17,74 +17,6 @@ import { TranslateService } from '@ngx-translate/core'; | |||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Structure of a tag cloud returned by WS. |  | ||||||
|  */ |  | ||||||
| export interface CoreTagCloud { |  | ||||||
|     tags: CoreTagCloudTag[]; |  | ||||||
|     tagscount: number; |  | ||||||
|     totalcount: number; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Structure of a tag cloud tag returned by WS. |  | ||||||
|  */ |  | ||||||
| export interface CoreTagCloudTag { |  | ||||||
|     name: string; |  | ||||||
|     viewurl: string; |  | ||||||
|     flag: boolean; |  | ||||||
|     isstandard: boolean; |  | ||||||
|     count: number; |  | ||||||
|     size: number; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Structure of a tag collection returned by WS. |  | ||||||
|  */ |  | ||||||
| export interface CoreTagCollection { |  | ||||||
|     id: number; |  | ||||||
|     name: string; |  | ||||||
|     isdefault: boolean; |  | ||||||
|     component: string; |  | ||||||
|     sortoder: number; |  | ||||||
|     searchable: boolean; |  | ||||||
|     customurl: string; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Structure of a tag index returned by WS. |  | ||||||
|  */ |  | ||||||
| export interface CoreTagIndex { |  | ||||||
|     tagid: number; |  | ||||||
|     ta: number; |  | ||||||
|     component: string; |  | ||||||
|     itemtype: string; |  | ||||||
|     nextpageurl: string; |  | ||||||
|     prevpageurl: string; |  | ||||||
|     exclusiveurl: string; |  | ||||||
|     exclusivetext: string; |  | ||||||
|     title: string; |  | ||||||
|     content: string; |  | ||||||
|     hascontent: number; |  | ||||||
|     anchor: string; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Structure of a tag item returned by WS. |  | ||||||
|  */ |  | ||||||
| export interface CoreTagItem { |  | ||||||
|     id: number; |  | ||||||
|     name: string; |  | ||||||
|     rawname: string; |  | ||||||
|     isstandard: boolean; |  | ||||||
|     tagcollid: number; |  | ||||||
|     taginstanceid: number; |  | ||||||
|     taginstancecontextid: number; |  | ||||||
|     itemid: number; |  | ||||||
|     ordering: number; |  | ||||||
|     flag: number; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Service to handle tags. |  * Service to handle tags. | ||||||
|  */ |  */ | ||||||
| @ -343,3 +275,71 @@ export class CoreTagProvider { | |||||||
|             + contextId + ':' +  (recursive ? 1 : 0); |             + contextId + ':' +  (recursive ? 1 : 0); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag cloud returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagCloud = { | ||||||
|  |     tags: CoreTagCloudTag[]; | ||||||
|  |     tagscount: number; | ||||||
|  |     totalcount: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag cloud tag returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagCloudTag = { | ||||||
|  |     name: string; | ||||||
|  |     viewurl: string; | ||||||
|  |     flag: boolean; | ||||||
|  |     isstandard: boolean; | ||||||
|  |     count: number; | ||||||
|  |     size: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag collection returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagCollection = { | ||||||
|  |     id: number; | ||||||
|  |     name: string; | ||||||
|  |     isdefault: boolean; | ||||||
|  |     component: string; | ||||||
|  |     sortoder: number; | ||||||
|  |     searchable: boolean; | ||||||
|  |     customurl: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag index returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagIndex = { | ||||||
|  |     tagid: number; | ||||||
|  |     ta: number; | ||||||
|  |     component: string; | ||||||
|  |     itemtype: string; | ||||||
|  |     nextpageurl: string; | ||||||
|  |     prevpageurl: string; | ||||||
|  |     exclusiveurl: string; | ||||||
|  |     exclusivetext: string; | ||||||
|  |     title: string; | ||||||
|  |     content: string; | ||||||
|  |     hascontent: number; | ||||||
|  |     anchor: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag item returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagItem = { | ||||||
|  |     id: number; | ||||||
|  |     name: string; | ||||||
|  |     rawname: string; | ||||||
|  |     isstandard: boolean; | ||||||
|  |     tagcollid: number; | ||||||
|  |     taginstanceid: number; | ||||||
|  |     taginstancecontextid: number; | ||||||
|  |     itemid: number; | ||||||
|  |     ordering: number; | ||||||
|  |     flag: number; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -634,3 +634,21 @@ export class CoreUserProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by user_summary_exporter. | ||||||
|  |  */ | ||||||
|  | export type CoreUserSummary = { | ||||||
|  |     id: number; // Id.
 | ||||||
|  |     email: string; // Email.
 | ||||||
|  |     idnumber: string; // Idnumber.
 | ||||||
|  |     phone1: string; // Phone1.
 | ||||||
|  |     phone2: string; // Phone2.
 | ||||||
|  |     department: string; // Department.
 | ||||||
|  |     institution: string; // Institution.
 | ||||||
|  |     fullname: string; // Fullname.
 | ||||||
|  |     identity: string; // Identity.
 | ||||||
|  |     profileurl: string; // Profileurl.
 | ||||||
|  |     profileimageurl: string; // Profileimageurl.
 | ||||||
|  |     profileimageurlsmall: string; // Profileimageurlsmall.
 | ||||||
|  | }; | ||||||
|  | |||||||
| @ -81,126 +81,6 @@ export interface CoreWSAjaxPreSets { | |||||||
|     useGet?: boolean; |     useGet?: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Error returned by a WS call. |  | ||||||
|  */ |  | ||||||
| export interface CoreWSError { |  | ||||||
|     /** |  | ||||||
|      * The error message. |  | ||||||
|      */ |  | ||||||
|     message: string; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Name of the exception. Undefined for local errors (fake WS errors). |  | ||||||
|      */ |  | ||||||
|     exception?: string; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * The error code. Undefined for local errors (fake WS errors). |  | ||||||
|      */ |  | ||||||
|     errorcode?: string; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * File upload options. |  | ||||||
|  */ |  | ||||||
| export interface CoreWSFileUploadOptions extends FileUploadOptions { |  | ||||||
|     /** |  | ||||||
|      * The file area where to put the file. By default, 'draft'. |  | ||||||
|      */ |  | ||||||
|     fileArea?: string; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Item ID of the area where to put the file. By default, 0. |  | ||||||
|      */ |  | ||||||
|     itemId?: number; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Structure of warnings returned by WS. |  | ||||||
|  */ |  | ||||||
| export type CoreWSExternalWarning = { |  | ||||||
|     /** |  | ||||||
|      * Item. |  | ||||||
|      * @type {string} |  | ||||||
|      */ |  | ||||||
|     item?: string; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Item id. |  | ||||||
|      * @type {number} |  | ||||||
|      */ |  | ||||||
|     itemid?: number; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * The warning code can be used by the client app to implement specific behaviour. |  | ||||||
|      * @type {string} |  | ||||||
|      */ |  | ||||||
|     warningcode: string; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Untranslated english message to explain the warning. |  | ||||||
|      * @type {string} |  | ||||||
|      */ |  | ||||||
|     message: string; |  | ||||||
| 
 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Structure of files returned by WS. |  | ||||||
|  */ |  | ||||||
| export type CoreWSExternalFile = { |  | ||||||
|     /** |  | ||||||
|      * File name. |  | ||||||
|      * @type {string} |  | ||||||
|      */ |  | ||||||
|     filename?: string; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * File path. |  | ||||||
|      * @type {string} |  | ||||||
|      */ |  | ||||||
|     filepath?: string; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * File size. |  | ||||||
|      * @type {number} |  | ||||||
|      */ |  | ||||||
|     filesize?: number; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Downloadable file url. |  | ||||||
|      * @type {string} |  | ||||||
|      */ |  | ||||||
|     fileurl?: string; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Time modified. |  | ||||||
|      * @type {number} |  | ||||||
|      */ |  | ||||||
|     timemodified?: number; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * File mime type. |  | ||||||
|      * @type {string} |  | ||||||
|      */ |  | ||||||
|     mimetype?: string; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Whether is an external file. |  | ||||||
|      * @type {number} |  | ||||||
|      */ |  | ||||||
|     isexternalfile?: number; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * The repository type for external files. |  | ||||||
|      * @type {string} |  | ||||||
|      */ |  | ||||||
|     repositorytype?: string; |  | ||||||
| 
 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * This service allows performing WS calls and download/upload files. |  * This service allows performing WS calls and download/upload files. | ||||||
|  */ |  */ | ||||||
| @ -948,3 +828,127 @@ export class CoreWSProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Error returned by a WS call. | ||||||
|  |  */ | ||||||
|  | export interface CoreWSError { | ||||||
|  |     /** | ||||||
|  |      * The error message. | ||||||
|  |      */ | ||||||
|  |     message: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Name of the exception. Undefined for local errors (fake WS errors). | ||||||
|  |      */ | ||||||
|  |     exception?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The error code. Undefined for local errors (fake WS errors). | ||||||
|  |      */ | ||||||
|  |     errorcode?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * File upload options. | ||||||
|  |  */ | ||||||
|  | export interface CoreWSFileUploadOptions extends FileUploadOptions { | ||||||
|  |     /** | ||||||
|  |      * The file area where to put the file. By default, 'draft'. | ||||||
|  |      */ | ||||||
|  |     fileArea?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Item ID of the area where to put the file. By default, 0. | ||||||
|  |      */ | ||||||
|  |     itemId?: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of warnings returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreWSExternalWarning = { | ||||||
|  |     /** | ||||||
|  |      * Item. | ||||||
|  |      */ | ||||||
|  |     item?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Item id. | ||||||
|  |      */ | ||||||
|  |     itemid?: number; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The warning code can be used by the client app to implement specific behaviour. | ||||||
|  |      */ | ||||||
|  |     warningcode: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Untranslated english message to explain the warning. | ||||||
|  |      */ | ||||||
|  |     message: string; | ||||||
|  | 
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of files returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreWSExternalFile = { | ||||||
|  |     /** | ||||||
|  |      * File name. | ||||||
|  |      */ | ||||||
|  |     filename?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * File path. | ||||||
|  |      */ | ||||||
|  |     filepath?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * File size. | ||||||
|  |      */ | ||||||
|  |     filesize?: number; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Downloadable file url. | ||||||
|  |      */ | ||||||
|  |     fileurl?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Time modified. | ||||||
|  |      */ | ||||||
|  |     timemodified?: number; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * File mime type. | ||||||
|  |      */ | ||||||
|  |     mimetype?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Whether is an external file. | ||||||
|  |      */ | ||||||
|  |     isexternalfile?: number; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The repository type for external files. | ||||||
|  |      */ | ||||||
|  |     repositorytype?: string; | ||||||
|  | 
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by date_exporter. | ||||||
|  |  */ | ||||||
|  | export type CoreWSDate = { | ||||||
|  |     seconds: number; // Seconds.
 | ||||||
|  |     minutes: number; // Minutes.
 | ||||||
|  |     hours: number; // Hours.
 | ||||||
|  |     mday: number; // Mday.
 | ||||||
|  |     wday: number; // Wday.
 | ||||||
|  |     mon: number; // Mon.
 | ||||||
|  |     year: number; // Year.
 | ||||||
|  |     yday: number; // Yday.
 | ||||||
|  |     weekday: string; // Weekday.
 | ||||||
|  |     month: string; // Month.
 | ||||||
|  |     timestamp: number; // Timestamp.
 | ||||||
|  | }; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user